java基础-线程

1.进程是一个应用程序。线程就是这个进程中的一条执行路径,是单独的控制单元。

2.创建线程的两种方式:

(1)继承Thread,重写run(),就是线程。

(2)实现Runnable接口,重写run方法,将Runnable对象传入Thread的参数中。  但是推荐使用第二种方式。因为java类只能单继承,可以多实现。

一、线程的状态

1.状态:新建,就绪,运行,阻塞,挂起,终止

2.状态转换图


(1)new之后就是新建状态,start之后就是就绪状态。只要获得cpu就可以执行了。

(2)就绪状态的获得cpu之后就是运行状态了,如果时间片用完,或者发生Thread.yield之后会回到就绪状态。起到的作用就相当于等其他的线程达到就绪状态。

(3)运行状态发生Thread.sleep或者t.join(让t先执行完,某一个线程先释放cpu)之后,进入阻塞状态,sleep时间过后,或者join的线程运行完后,回到就绪状态。

(4)线程wait之后进入等待状态,阻塞

(5)等待状态的线程notify或notifyAll之后进入锁池状态,阻塞

(6)运行状态的线程synchronized之后进入锁池状态,阻塞

(7)run运行完或者main运行完之后进入终止状态

(8)运行状态suspend之后进入挂起状态,需要再执行resume()让他到达就绪状态

(9)锁池状态的得到锁之后进入就绪状态

从状态转换图中可以看出主要有两种资源:(1)CPU资源(2)所资源。 并且可以看出来只有wait()的时候才会释放锁资源,其他的操作如果有锁资源的话都不会进行释放。

notify唤醒一个线程,但是是那个不确定,所以实际中使用notifyAll。

3.解释:wait和notify/notifyAll是Object的方法?

(1)解释对象锁:任何对象都有锁,这个对象锁就有等待池和锁池。等待池是wait之后的线程池。锁池是notify之后等待锁的线程

(2)wait释放锁,所以必须有锁,java中任何对象都有锁,所以调用对象的wait就实现了释放锁,阻塞。然后调用对象的notify/notifyAll就可以唤醒这个对象锁的等待池中的等待这个对象锁的线程了

4.wait和notify为什么要在synchronized中?

因为在synchronized方法或块中的线程才有锁,有锁才能释放锁。才能唤醒对象锁的等待池中的线程来拿这把锁

5.线程的名字

得到名字:Thread.currentThread().getName()

设置名字:t.setName()

二、线程死锁

1.产生自锁的原因

(1)资源不够

(2)进程推进顺序不合理

(3)资源分配不当

2.产生死锁的必要条件

(1)互斥条件

(2)请求和保持

(3)不可剥夺条件

(4)环路等待

3.避免死锁

 (1)按同一顺序访问对象。
 (2)避免事务中的用户交互。
 (3)保持事务简短并处于一个批处理中。

 (4)使用较低的隔离级别。

死锁的例子:

public class Test2 extends Test1{
	public static void main(String[] args) {
		//虽然两个线程访问的是不同的数据,不是共享区,但是他们需要使用的锁是static的,是唯一的
		//这也可以构成死锁。
		Thread t1 = new Thread(new T(true));
		Thread t2 = new Thread(new T(false));
		t1.start();
		
		t2.start();
	}
}
class T implements Runnable{
	boolean flag = true;
	T(boolean flag){
		this.flag = flag;
	}
	public void run() {
		if(flag){
			while(true){
				//持有自己的锁,还想要别人的锁
				synchronized(MyLock.lock1){
					System.out.println("lock1 if");
					synchronized(MyLock.lock2){
						System.out.println("lock2 if");
					}
				}
			}
		}else{
			while(true){
				synchronized(MyLock.lock2){
					System.out.println("lock2 else");
					synchronized(MyLock.lock1){
						System.out.println("lock1 else");
					}
				}
			}
		}
	}
}
class MyLock{
	public static Object lock1 = new Object();
	public static Object lock2 = new Object();
}

三、多线程并发

1.资源共享

(1)使用static,在每一个Runnable的成员变量中,缺点是生命周期太长

(2)共享资源单独出来作为一个类,单例设计模式。当作不同线程构造函数参数传入。多窗口售票例子

2.多线程并发访问

(1)使用的是同一把锁的时候会发生安全问题。使用同步机制解决并发访问的安全问题。

(2)同步机制就是加锁。使用加锁的方法,或者加锁的代码块。也就是同步方法或者同步代码块。加锁的缺点:需要判断锁,所以效率低,消耗资源。

(3)锁有哪些:非静态方法加锁,锁对象就是this;静态方法加锁或者同步代码块中为(A.class)锁对象是字节码对象Class

3.多线程通信

多线程并发访问,可以做相同的事,也可以做不同的事。保证并发安全,加锁的时候就要保证他们之间可以通信,使用wait/notify/notifyAll。

a.生产者与消费者Synchronized版本:

public class Test2{
	public static void main(String[] args) {
		Res r = new Res();
		Producer p = new Producer(r);
		Customer c = new Customer(r);Thread t1 = new Thread(p);
		Thread t3 = new Thread(p);
		Thread t2 = new Thread(c);
		Thread t4 = new Thread(c);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Res {
	private String name;
	private int count = 1;
	private boolean flag = false;
	
	public synchronized void set(String name){
		//注意:这里一定要使用while,这里的while没有任何实际的意义,只是为了安全,加强监控
                while(flag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name+"--"+count++;//这样得到的count++是1
		//注意:下面的一句容易出现的问题是:this.name 写成了name,name是局部的变量,this.name是成员变量
		System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
		flag = true;
		this.notifyAll();
		}
       (*)获得当前线程的名字:Thread.currentThread().getName()
	public synchronized void out(){
		while(!flag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"------------消费了"+name);
		flag = false;
		this.notifyAll();
	}
}
class Producer implements Runnable{
	private Res r = null;
	Producer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		//这里的while是实际的物理逻辑
while(true){
			r.set("商品");
		}
	}
}
class Customer implements Runnable{
	private Res r = null;
	Customer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.out();
		}
	}

notify只能通知一个线程,具体通知哪一个是未知的。notifyAll会通知全部的线程,但是我们想通知对象的线程,所以使用notiFy和notifyAll不一定是最好的选择。

b.Lock版本的线程通信

只通知对方的线程。所在的包:java.util.concurrent.locks

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 生产者-消费者问题
 * @author huer
 *
 */
public class Test2{
	public static void main(String[] args) {
		Res r = new Res();
		Producer p = new Producer(r);
		Customer c = new Customer(r);
		Thread t1 = new Thread(p);
		Thread t3 = new Thread(p);
		Thread t2 = new Thread(c);
		Thread t4 = new Thread(c);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
//共享资源,里面的属性都是共享的
class Res {
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	private Condition con_pro = lock.newCondition();
	private Condition con_cus = lock.newCondition();
//生产的方法
	public void set(String name){
		lock.lock();
		while(flag){
			try {
				con_pro.await();//生产者先wait()
				this.name = name+"--"+count++;
				System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
				flag = true;
				con_cus.signal();//唤醒消费者
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}
		
	}
//消费的方法
	public void out(){
		//先加锁
		lock.lock();
		while(!flag){
			//使用wait()
			try {
				con_cus.await();//先暂停消费
				System.out.println(Thread.currentThread().getName()+"------------消费了"+name);
				flag = false;
				//唤醒
				con_pro.signal();//唤醒生产者
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}	
	}
}
class Producer implements Runnable{
	private Res r = null;
	Producer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
        //这里的while是实际的业务逻辑,一直生产
		while(true){
			r.set("商品");
		}
	}
}
class Customer implements Runnable{
	private Res r = null;
	Customer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.out();
		}
	}
}

4.线程结束

(1)控制run结束,用一个boolean类型的变量

public class Test2{
	public static void main(String[] args) {
		StopThread s = new StopThread();
		
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		t1.start();
		t2.start();
		int num = 0;
		while(true){
			if(num == 60){
				s.change(false);//控制线程的结束
				break;
			}
			System.out.println(Thread.currentThread().getName()+"------"+num);
			num ++;
		}
	}
}
class StopThread implements Runnable{
	//引入控制循环的标志
	private boolean flag = true;
	public void run() {
		//这里的线程的run是while,是一直循环执行的方法
                while(flag){
			System.out.println(Thread.currentThread().getName()+"-----run");
		}
	}
	//还要有可以改变标志的操作
	public void change(boolean flag){
		this.flag = flag;
	}
}

(2)线程中有wait冻结无法停止,用interrupt打破wait

public class Test2{
	public static void main(String[] args) {
		StopThread s = new StopThread();
		
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		t1.start();
		t2.start();
		int num = 0;
		while(true){
			if(num == 60){
				//使冻结的线程可以运行
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"------"+num);
			num ++;
		}
	}
}
class StopThread implements Runnable{
	private boolean flag = true;
	public synchronized void run() {
		while(flag){
			try {
				wait();
			} catch (InterruptedException e) {
				System.out.println(Thread.currentThread().getName()+"-----Exception");
				flag = false;//使冻结的线程可以完整的运行
			}
			System.out.println(Thread.currentThread().getName()+"-----run");
		}
	}
	//还要有可以改变标志的操作
	public void change(boolean flag){
		this.flag = flag;
	}
}

5.守护线程

java线程分为用户线程和守护线程。

用户线程全部结束的时候,守护线程会和虚拟机一起退出。用户线程没有结束的时候守护线程不会退出。

注意三点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

(1)主线程运行一次,守护线程是while循环

package cn.bishe.solrj;

import java.io.File;
import java.io.IOException;
public class SolrJManager {
	public static void main(String[] args) throws IOException{  
	        Thread mainThread = new Thread(new Runnable(){  
	            public void run()  
	            {  
	                Thread childThread = new Thread(new ClildThread());  
	                childThread.setDaemon(true);  
	                childThread.start();  
	                System.out.println("I'm main thread...");  
	            }  
	        });  
	        mainThread.start();  
	    }  
}
class ClildThread implements Runnable  
{  
    public void run()  
    {  
        while(true)  
        {  
            System.out.println("I'm child thread..");  
            try  
            {  
                Thread.sleep(1000);  
            }  
            catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            }  
        }  
    }  
}
/*运行结果
 * I'm main thread...
 * I'm child thread..
 */

从运行结果中可以看出来,主线程运行完了之后,守护线程虽然是while,但是也会退出。

(2)主线程有while循环不会退出,那么守护线程也不会退出

package cn.bishe.solrj;

import java.io.File;
import java.io.IOException;
public class SolrJManager {
	public static void main(String[] args) throws IOException{  
	Thread mainThread = new Thread(new Runnable(){   
            public void run()  
            {  
                Thread childThread = new Thread(new ClildThread());  
                childThread.setDaemon(true);  
                childThread.start();  
                System.out.println("I'm main thread...");  
            }  
        });  
        mainThread.start();  
        
        Thread otherThread = new Thread(new Runnable(){   
            public void run()  
            {  
                while(true)  
                {  
                    System.out.println("I'm other user thread...");  
                    try  
                    {  
                       Thread.sleep(1000);  
                    }  
                    catch (InterruptedException e)  
                    {  
                        e.printStackTrace();  
                    }  
                }  
            }  
        });  
        otherThread.start();  
        
	}  
}
class ClildThread implements Runnable  
{  
    public void run()  
    {  
        while(true)  
        {  
            System.out.println("I'm child thread..");  
            try  
            {  
                Thread.sleep(1000);  
            }  
            catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            }  
        }  
    }  
}
/*运行结果
I'm main thread...
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..
I'm other user thread...
I'm child thread..后面两句无限循环
 */

从运行结果中可以看出来主线程没有结束的时候,守护线程也不会结束。

守护线程掌握的还不是很多。

知识点:

(1)join方法

第一种情况:

public static void main(String[] args) {
		StopThread s = new StopThread();
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		t1.start();
		//情况一、t1要执行权,main冻结,把执行权交出来了(t2.start()也是main的执行语句),此时只有一个线程t1。所以interrupt()方法打断冻结,打断的就是main的冻结。
		//t1执行完后,释放CPU,main执行t2.start(),此时t2和main共同抢夺CPU,他们交替进行
		try {
		    t1.join();
		} catch (InterruptedException e) {
		    e.printStackTrace();
		}
		t2.start();
		for(int i =0;i<70;i++){
			System.out.println("main---"+i);
		}
	}

第二种情况:

public static void main(String[] args) {
		StopThread s = new StopThread();
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		//下面的这一句是main线程在获得执行权
		t1.start();
		t2.start();
		//情况二、main开启了t1,t2,此时main,t1,t2三个线程共同抢夺CPU的执行权
		//t1,t2启动完成之后,可能main还拿着线程,t1.join()之后,main释放了CPU,此时活者的线程有t1,t2
		//所以这个时候是t1,t2是共同抢夺CPU,他俩都执行完后,main才能获得CPU,所以main是最后才执行完的
		try {
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for(int i =0;i<70;i++){
			System.out.println("main---"+i);
		}
	}

(2)线程优先级

线程优先级:1-10,默认是5。设置优先级线程优先执行的可能性大,但是不是绝对的。

设置线程优先级:t.setPriority(8);

知识点:yield方法和sleep方法和wait方法的区别

参考:https://www.jianshu.com/p/25e959037eed

(1)都可以使线程暂停
(2)sleep()和yield()方法是定义在Thread类中,而wait()方法是定义在Object类中的
(3)wait与sleep的区别
wait()是用于线程间通信的,而sleep()是用于短时间暂停当前线程;
当一个线程调用wait()方法的时候,会释放它锁持有的对象的管程和锁,但是调用sleep()方法的时候,不会释放他所持有的管程;
Java中的wait方法应在同步环境中调用,但是sleep方法不需要;
Thread.sleep()方法是一个静态方法,作用在当前线程上。但是wait方法是一个实例方法,并且只能在其他线程调用本实例(同一个对象)的notify()方法时被唤醒;
进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒;
使用sleep方法时,被暂停的线程在被唤醒之后会立即进入就绪态(Runnable state),但是使用wait方法的时候,被暂停的线程会首先获得锁,然后再进入就绪态;
wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真。但是sleep仅仅让你的线程进入睡眠状态;

根据你的需求,如果你需要暂定你的线程一段特定的时间就使用sleep()方法,如果你想要实现线程间通信就使用wait()方法。

(4)yield方法与sleep方法的区别
它仅仅释放线程所占有的CPU资源,从而让其他线程有机会运行,但是并不能保证某个特定的线程能够获得CPU资源。谁能获得CPU完全取决于调度器,在有些情况下调用yield方法的线程甚至会再次得到CPU资源。yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。执行了yield方法的线程什么时候会继续运行由线程调度器来决定,不同的厂商可能有不同的行为。yield方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用yield方法时会放弃CPU。所以,依赖于yield方法是不可靠的,它只能尽力而为。
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值