Java多线程编程(基础篇)
一.进程和线程的区别:
进程:当前计算机正在运行的程序,进程是cpu分配资源的基本单位,一个进程至少有一个线程。
线程: 计算机中任务调度和最小的执行单元,一个线程也被称为轻量级进程。
Java多线程:在单个程序运作的过程中同时运作多个线程,完成不同的工作,称为多线程。
引入线程的好处:Java虚拟机允许应用程序并发的运行多个线程,引入线程可以减少程序并发时的cpu的开销。
二.Java的运行状态图:
初始状态(被创建):创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
运行状态:获得CPU执行权,正在执行的线程。
等待状态:进入等待态的线程会暂时释放CPU执行权,并释放资源。
阻塞状态:处于等待状态的线程,会不断地请求资源,直到请求到资源,才从阻塞状态转换到运行状态。
销亡状态:线程执行结束。
三.如何自定义一个线程?
1.继承一个线程类。
public class ThreadDemo1 { public static void main(String[] args) { for (int i = 0; i < 5; i++) { System.out.println("main...i=" + i); } Tred t = new Tred(); //创建一个线程 t.start(); //start()开启线程 } } class Tred extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("thread...i=" + i); } } }
2.实现一个接口。
public class ShowTicket { public static void main(String[] args) { Tree t=new Tree(); Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); t1.start(); t2.start(); t3.start(); } } class Tree implements Runnable{ //继承一个接口 static int ticket=100; @Override public void run() { while(true) { try { Thread.sleep(30); }catch(Exception e) {} if(ticket>0) { System.out.println(Thread.currentThread().getName()+ "正在出售第"+ticket--+"张票"); } } } }
总结:
无论是继承Thread还是实现接口,都要重写run()方法,因为run方法中定义了线程的执行内,而且此方法由JVM自动调用。
顺便说一下Java多线程的执行路径:
程序的执行流程将不会按照原有单线程的执行流程执行,有可能会出现多个线程之间互相打断的情况。
四.synchronized关键字
synchronized:同步
语法:synchronized(对象锁){ 要锁的代码 }
作用:保证线程执行的原子性,也就是说,当前线程执行完毕后,其他线程方可执行。
注意:此处对象锁锁的不是代码块,锁的是对象。当对象发生改变时,同步会失效。
synchronized保证程序原子性的代码实例:
public class ShowTicket1 { public static void main(String[] args) { Windows w=new Windows(); Thread t1=new Thread(w); Thread t2=new Thread(w); Thread t3=new Thread(w); t1.start(); t2.start(); t3.start(); } } class Windows implements Runnable{ private int ticket =100; Object obj=new Object(); @Override public void run() { while(true) { synchronized(obj) {//原子性 互斥锁,不可再分,代码块必须执行完,保证了代码的完整性。 if(ticket>0) { //try{TimeUnit.SECONDS.sleep(0);}catch(Exception e){} 睡眠的另一种写法 try{Thread.sleep(50);}catch(Exception e) {} System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket--+"张票"); } } } } }
知识点:
1. 同步和非同步方法能否同时调用?
可以
在同步方法在执行过程中,允许其他线程调用非同步方法。
2.验证同步方法的对象锁是this
思路:开启两个线程,让一个进入同步代码块,另一个进入同步方法,设置一个中间变量boolean flag
实例代码:
public class SynchronizedDemo { public static void main(String[] args) { Tt t = new Tt(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try { Thread.sleep(50); } catch (Exception e) { } t.flag=false; t2.start(); } } class Tt implements Runnable { int count = 10; Object obj = new Object(); boolean flag=true; @Override public void run() { if (flag) { while (true) { synchronized (this) { try { Thread.sleep(50); } catch (Exception e) { } if (count > 0) System.out.println(Thread.currentThread().getName() + "count=" + count--); } } } else { while(true) method(); } } public synchronized void method() { while (true) { try { Thread.sleep(50); } catch (Exception e) { } if (count > 0) System.out.println(Thread.currentThread().getName() + "count=" + count--); } }
3.如果同步代码的对象锁是this,那么这个方法可以写为同步方法。
4.线程死锁:线程死锁:
假设有A,B两个死锁,a线程的执行需要获取b线程的对象锁,b线程的执行需要获取a线程的对象锁
实例代码:
public class DeathLock { public static void main(String[] args) { new Thread(new DL(true)).start(); new Thread(new DL(false)).start(); } } class Lock { static final Object LOCKA = new Object(); static final Object LOCKB = new Object(); } class DL implements Runnable { boolean flag; public DL(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (Lock.LOCKA) { System.out.println("if locka run"); synchronized (Lock.LOCKB) { System.out.println("if locka run"); } } } else { synchronized (Lock.LOCKB) { System.out.println("else lockb run"); synchronized (Lock.LOCKA) { System.out.println("else locka run"); } } } } }
结果分析:程序出现死锁,彼此都在等待对方释放资源。
5.如果程序运行过程中,出现了异常,则该对象锁会被释放。
实例代码如下:
public class Demo4 { public static void main(String[] args) { Demo4 d=new Demo4(); new Thread(()->d.method(),"d1").start(); try { Thread.sleep(30); }catch(Exception e) {} new Thread(()->d.method(),"d2").start(); } int num=10; synchronized void method() { System.out.println(Thread.currentThread().getName()+"start"); while(true) { num++; System.out.println(Thread.currentThread().getName()+"num="+ num); try { Thread.sleep(30); }catch(Exception e) {} if(num==15) { //try { int num=1/0; //}catch(Exception e) {} } } } }
结果分析:
当发生异常时,当前的对象锁会被释放。所以,在并发过程中出现异常,需要特别小心,否则会出现执行结果不一致的情况
如何解决?
捕获该异常。
6.最好不要用字符串常量作为对象锁。
volatile关键字:可保证线程透明,不具有原子性,但是效率比较高。
可保证线程透明的原因,在此简单的说一下:由于Java的内存模型可知,多个线程之间共享的资源被存放在内存中,并且每个线程都有自己独立的工作区,Java默认每个线程都将获取到内存中的副本,并且拿着这个副本进行操。当有线程对当前变量进行修改时,其他线程将无法感知,所以不会停止运行。所以,使用volatile关键字,会通知所有的线程变量发生了改变,让所有线程都获取到变量的修改值。
注意:volatile不能保证程序运行的原子性,所以,不能代替synchronized。
实例代码如下:
public class Demo6 { public static void main(String[] args) { Demo6 d = new Demo6(); List<Thread> list = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { list.add(new Thread(d::method, "list-" + i)); } list.forEach((o) -> o.start());//让集合中的10个线程都启动 list.forEach((o) -> { try { o.join();//让集合中的10个线程先执行完,在执行main线程 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); System.out.println(d.num); } volatile int num = 0; synchronized void method() { for (int i = 0; i < 10000; i++) { num++; } } }
五:
AtomicXXX类本身就是原子性的,且有很多原子性的方法,但是不能保证多个原子性的方法连续使用还是原子性的。
由于++ --是不具有原子性的,所以针对这一问题,显得十分高效。
AtomicXXX类:执行效率比synchronized votalie高
实例代码:
public class Demo8 { public static void main(String[] args) { Demo8 d=new Demo8(); List<Thread> list=new ArrayList<Thread> (); for(int i=0;i<10;i++) { list.add(new Thread(d::method,"list-"+i)); } list.forEach((o)->o.start()); list.forEach((o)->{ try { o.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); System.out.println(d.num); } AtomicInteger num=new AtomicInteger(0); void method() { for(int i=0;i<20;i++) { num.incrementAndGet();//count++ } } }