大纲:
基本概念:程序、进程、线程线程的创建和使用线程的生命周期线程的同步线程的通信JDK5.0新增线程创建方式目录
一、基本概念:
二、线程的创建
1、方法一:继承Thread类
2、创建线程的方法二:实现Runnable接口
Thead和runnable两种创建线程方式对比
三、线程的方法:
四、线程的生命周期:
五、解决多线程安全问题:
方式一:同步代码块
方式二:同步方法
方式三:JDK5.0新增方法:Lock锁
synchronized与Lock的对比:
六、线程之间的通信
七、sleep和wait方法的异同:
八、生产者和消费者问题:
九、新增的创建多线程方式:
1、实现Callable接口
2、使用线程池方式:
1、程序:一段静态的代码
2、进程:是程序的一次执行过程,或者是正在运行的一个程序,是一个动态的过程。有它自身的产生、存在、和消亡的过程——生命周期
进程作为资源分配的单位,系统会在运行时为每个进程分配不同的内存区域3、线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更便捷、高效。但多个线程操作共享的系统资源可能会带来安全隐患。4、并发:多线程
5、并行:多进程
使用匿名对象、子类的方式创建线程:
public class ThreadTest06 { public static void main(String[] args) { new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + " " + i); } } } }.start(); } }继承Thread类
public class ticketThread { public static void main(String[] args) { window1 t1 = new window1("窗口一"); window1 t2 = new window1("窗口二"); window1 t3 = new window1("窗口三"); t1.start(); t2.start(); t3.start(); } } class window1 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { if (ticket > 0) { System.out.println(getName() + ":" + ticket); ticket--; } else { break; } } } public window1(String name) { super(name); } }实现runnable接口:
public class ticketRunnable { public static void main(String[] args) { win w = new win(); Thread t1 = new Thread(w, "窗口一"); Thread t2 = new Thread(w, "窗口二"); Thread t3 = new Thread(w, "窗口三"); t1.start(); t2.start(); t3.start(); } } class win implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+" " +ticket); ticket--; }else { break; } } } }两种方式对比:
开发中优选选择:实现Runnable接口的方式
原因:
1、实现接口的方式没有类的单继承性局限
2、实现的方式更适合来处理多个线程共享数据的情况,例如上面两个代码的ticket。继承Thead的方式必须把ticket声明为static类型,而实现runnable的方式不需要,因为其天生就只有一份
两种方式的联系:翻看Thread的源码就知道,其实Thread类也继承了Runnable接口。
给线程命名方式一:使用setName()方法
public class ThreadTest07 { public static void main(String[] args) { NameThread t1 = new NameThread(); t1.setName("线程一"); t1.start(); } } class NameThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+" "+i); } } } }线程命名方式二:重写构造方法
public class ThreadTest07 { public static void main(String[] args) { NameThread t1 = new NameThread("线程一"); t1.start(); } } class NameThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+" "+i); } } } public NameThread(String name){ super(name); } }
1、yield()方法:暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法。自己也会重新加入竞争
2、join()方法:在线程a中调用b线程的join方法,此时线程a就进入阻塞状态,直到线程b执行完后,线程a才结束阻塞状态
public class ThreadTest07 { public static void main(String[] args) { NameThread t1 = new NameThread("线程一"); t1.start(); for (int i = 0; i < 100; i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+" "+i); } if(i==2){ try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class NameThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+" "+i); } } } public NameThread(String name){ super(name); } }3、sleep(long millitime):让当前线程睡眠指定毫秒时长,在指定的millitime毫秒时间内,线程是阻塞状态
4、isAlive() 判断当前线程是否存活
线程的优先级
用图总结吧
说明:
同步监视器,俗称:锁。要求多个线程必须使用同一把锁如果是使用实现runnable的方式实现锁,则同步监视器可以是 this如果是使用继承thread类的方式实现锁,则同步监视器可以是同步代码块所在类的 class对象单例模式的线程安全实现方式:
class Dog{ private static Dog instance = null; private Dog(){ } public static Dog getInstance(){ if(instance ==null){ synchronized (Dog.class){ if(instance == null){ instance = new Dog(); } } } return instance; } }而synchronized只有非公平锁
public class ticketRunnable { public static void main(String[] args) { win w = new win(); Thread t1 = new Thread(w, "窗口一"); Thread t2 = new Thread(w, "窗口二"); Thread t3 = new Thread(w, "窗口三"); t1.start(); t2.start(); t3.start(); } } class win implements Runnable { private int ticket = 100; // 这里可以传入一个boolean类型的参数,若是true则此锁为公平锁,否则为非公平锁 // 公平锁的含义是在外面等待的线程在可以执行后按照先后顺序执行 private ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + ticket); ticket--; } else { break; } } finally { lock.unlock(); } } } }lock还有 如下两种方法:
tryLock(5, TimeUnit.SECONDS); lockInterruptibly();优先使用顺序:Lock -> 同步代码块(已经进入了方法体,分配了相应资源)-> 同步方法(在方法体之外)
1、syncronized最后会进入等待队列,不占用cpu。lock一般实现方式是cas(类似于自旋锁)会不停的轮询能不能拿到锁,会占用cpu
2、什么时候用自旋锁什么时候用重量级锁:
a. 线程少-自旋锁 线程多-重量级锁,即进入等待队列
b. 操作消耗时间长-重量级锁
交替打印100以内的数字
public class NumSkip { public static void main(String[] args) { Numb n = new Numb(); Thread t1 = new Thread(n,"打印机一:"); Thread t2 = new Thread(n,"打印机二:"); t1.start(); t2.start(); } } class Numb implements Runnable{ private int num = 1; @Override public void run() { while (true){ synchronized (this) { if (num <= 100) { notify(); try { Thread.sleep(100); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + num); num++; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }涉及到三个方法:
wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器notify(): 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级最高的那个notifyAll(): 一旦执行此方法,就会唤醒所有被wait的线程注意:
三种方法只能使用在同步代码块中或者同步方法中,在Lock方式的线程中不能使用三种方法调用者必须是同步代码块或同步方法中的同步监视器,否则会报错wait()、notify()、notifyAll()三个方法都是在Object类中声明的,不是在Thread类中声明的。原因:因为其三个的调用则必须是同步方法或者同步代码块中的同步监视器,二同步监视器又可以是任务类型的,所以这三个方法不能声明在Thread类中,声明在Object类中,任何一个类都继承了Object类,也就拥有了这三个方法。相同点:都会使得线程进入阻塞状态
不同点:
sleep不会释放锁,而wait会释放锁sleep是声明在Thead类中,而wait是声明在Object类中sleep可以在任何场景下使用,而wait必须在同步代码块中使用sleep使用interrupt唤醒,而wait使用notify唤醒十、释放锁和不释放锁的操作总结
volatile有两个特性:
保证线程可见性 — MESI— 缓存一致性协议 禁止指令重排序 DCL单例 public class Singleton2 { private static volatile Singleton2 INSTANCE; private Singleton2() { } public static Singleton2 getInstance() { if (INSTANCE == null) { synchronized (Singleton2.class) { if(INSTANCE==null) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } INSTANCE = new Singleton2(); } } } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 30; i++) { new Thread(new Runnable() { public void run() { System.out.println(Singleton2.getInstance().hashCode()); } }).start(); } } }volatile不能保证原子性,只能保证可见性
1、锁的粒度缩小
2、锁定某个对象o,如果o的属性发生改变,不影响使用,但是如果O变为另外一个对象,则锁定的对象发生改变。应避免将锁定对象的引用编程另外的对象
public class ChangeLock { Object o = new Object(); void m(){ synchronized (o){ while (true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { ChangeLock c = new ChangeLock(); new Thread(c::m,"t1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } Thread t2 = new Thread(c::m,"t2"); // 说对象发生改变,所以t2线程得意执行,如果注释这句话,线程2永远得不到执行机会 // c.o = new Object(); t2.start(); } }也有人叫其乐观锁。cas在unsafe类中,可以直接操作内存
凡是Atomic开头的都是通过cas保证线程安全
public class AtomicTest { AtomicInteger count = new AtomicInteger(0); void m(){ for (int i = 0; i < 1000; i++) { count.incrementAndGet(); } } public static void main(String[] args) { AtomicTest t = new AtomicTest(); ArrayList<Thread> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new Thread(t::m,"thread-"+i)); } list.forEach((o)->{o.start();}); list.forEach((o)-> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } }ABA问题:
如果要操作的为基本数据类型或者数值类型没问题,如果是引用类型则可能会发生问题。解决方法是加版本号
三种方式的对比:
public class CompareThread { static long count2 = 0L; static AtomicLong count1 = new AtomicLong(0L); static LongAdder count3 = new LongAdder(); public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { for (int i1 = 0; i1 < 100000; i1++) { count1.incrementAndGet(); } }); } long start = System.currentTimeMillis(); for (Thread thread : threads) thread.start(); for (Thread thread : threads) thread.join(); long end = System.currentTimeMillis(); System.out.println("count1-Atomic "+count1+" time: "+(end-start)); //------ Object o = new Object(); for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { for (int i1 = 0; i1 < 100000; i1++) { synchronized (o) { count2++; } } }); } start = System.currentTimeMillis(); for (Thread thread : threads) thread.start(); for (Thread thread : threads) thread.join(); end = System.currentTimeMillis(); System.out.println("count2 "+count2+" time: "+(end-start)); // -------- for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(()->{ for (int i1 = 0; i1 < 100000; i1++) { count3.increment(); } }); } start = System.currentTimeMillis(); for (Thread thread : threads) thread.start(); for (Thread thread : threads) thread.join(); end = System.currentTimeMillis(); System.out.println("count3-LongAdder "+count3+" time: "+(end-start)); } }结果对比:
为什么Atomic比synchronized要快,因为synchronized加锁了,有可能会去操作系统申请重量级锁。而Atomic内部使用了cas操作
LongAder为什么比Atomic还要快呢,是因为其内部使用了分段锁