一.进程和线程的区别:
进程:当前计算机正在运行的程序,进程是cpu分配资源的基本单位,一个进程至少有一个线程。
线程: 计算机中任务调度和最小的执行单元,一个线程也被称为轻量级进程。
Java多线程:在单个程序运作的过程中同时运作多个线程,完成不同的工作,称为多线程。
引入线程的好处:Java虚拟机允许应用程序并发的运行多个线程,引入线程可以减少程序并发时的cpu的开销。
二.Java的运行状态图:
初始状态(被创建):创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
运行状态:获得CPU执行权,正在执行的线程。
等待状态:进入等待态的线程会暂时释放CPU执行权,并释放资源。
阻塞状态:处于等待状态的线程,会不断地请求资源,直到请求到资源,才从阻塞状态转换到运行状态。
销亡状态:线程执行结束。
三.如何自定义一个线程?
1.继承一个线程类。
public classThreadDemo1 {public static voidmain(String[] args) {for (int i = 0; i < 5; i++) {
System.out.println("main...i=" +i);
}
Tred t= newTred(); //创建一个线程
t.start(); //start()开启线程
}
}class Tred extendsThread {public voidrun() {for (int i = 0; i < 5; i++) {
System.out.println("thread...i=" +i);
}
}
}
2.实现一个接口。
public classShowTicket {public static voidmain(String[] args) {
Tree t=newTree();
Thread t1=newThread(t);
Thread t2=newThread(t);
Thread t3=newThread(t);
t1.start();
t2.start();
t3.start();
}
}class Tree implementsRunnable{ //继承一个接口static int ticket=100;
@Overridepublic voidrun() {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 classShowTicket1 {public static voidmain(String[] args) {
Windows w=newWindows();
Thread t1=newThread(w);
Thread t2=newThread(w);
Thread t3=newThread(w);
t1.start();
t2.start();
t3.start();
}
}class Windows implementsRunnable{private int ticket =100;
Object obj=newObject();
@Overridepublic voidrun() {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 classSynchronizedDemo {public static voidmain(String[] args) {
Tt t= newTt();
Thread t1= newThread(t);
Thread t2= newThread(t);
t1.start();try{
Thread.sleep(50);
}catch(Exception e) {
}
t.flag=false;
t2.start();
}
}class Tt implementsRunnable {int count = 10;
Object obj= newObject();boolean flag=true;
@Overridepublic voidrun() {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 voidmethod() {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 classDeathLock {public static voidmain(String[] args) {new Thread(new DL(true)).start();new Thread(new DL(false)).start();
}
}classLock {static final Object LOCKA = newObject();static final Object LOCKB = newObject();
}class DL implementsRunnable {booleanflag;public DL(booleanflag) {this.flag =flag;
}
@Overridepublic voidrun() {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 classDemo4 {public static voidmain(String[] args) {
Demo4 d=newDemo4();new Thread(()->d.method(),"d1").start();try{
Thread.sleep(30);
}catch(Exception e) {}new Thread(()->d.method(),"d2").start();
}int num=10;synchronized voidmethod() {
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 classDemo6 {public static voidmain(String[] args) {
Demo6 d= newDemo6();
List list = new ArrayList();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 voidmethod() {for (int i = 0; i < 10000; i++) {
num++;
}
}
}
五:
AtomicXXX类本身就是原子性的,且有很多原子性的方法,但是不能保证多个原子性的方法连续使用还是原子性的。
由于++ --是不具有原子性的,所以针对这一问题,显得十分高效。
AtomicXXX类:执行效率比synchronized votalie高
实例代码:
public classDemo8 {public static voidmain(String[] args) {
Demo8 d=newDemo8();
List list=new ArrayList();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);voidmethod() {for(int i=0;i<20;i++) {
num.incrementAndGet();//count++
}
}
}