Java基础之多线程

一、概念

进程:是一个正在执行中的程序,每一个进程都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元
线程:就是进程中一个独立的控制单元,线程控制这进程的执行,一个进程中至少有一个线程
         如:javaVM启动的时候会有一个进程叫java.exe该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。其实更严谨的来说,javaVM启动的时候不止一个线程,还有一个垃圾回收的线程
多线程:一个进程中有多个线程同时运行

线程运行状态:


创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify();释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()


二、多线程的创建方式

第一种方式:自定义一个线程并继承Thread类。

步骤:

       1、定义类继承Thread。
       2、复写Thread类中的run方法。      
             目的:将自定义代码存储在run方法。让线程运行。
       3、调用线程的start方法,      
             该方法两个作用:启动线程,调用该线程的run方法。

为什么要覆盖run方法?
      Thread类用于描述线程该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法也就是说Thread类中的run方法,用于存储线程要运行的代码。

主要方法:

获取线程名称:getName()
设置线程名称:setName()或者构造方法
返回对当前正在执行的线程对象的引用:currentThread()

代码示例:

class SubThread extends Thread {
	public SubThread(String name) {
		super(name);
	}
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getName()+"--thread");
			//Thread.currentThread()==this
			System.out.println(Thread.currentThread().getName()+"thread");
		}
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
		SubThread st = new SubThread("one");//创建一个线程,并命名one
		st.start();//开启一个线程

		for (int i = 0; i < 100; i++) {
			System.out.println("hello");
		}
	}
}

第二种方式:实现Runnable接口

步骤:
      1、定义类实现Runnable接口
      2、覆盖Runnable接口中的run方法。
           将线程要运行的代码存放在该run方法中。
      3、通过Thread类建立线程对象。
      4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
           为什么要将Runnable接口的子类对象传递给Thread的构造函数。
           因为,自定义的run方法所属的对象是Runnable接口的子类对象。
           所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
      5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

实现方式和继承方式有什么区别?
        实现方式:避免了但继承的局限性 在定义线程时 建议使用此方式
两种方式的区别:
        继承Thread:线程代码存放在thread子类run方法中
        实现Runnable:线程代码存放在接口子类run方法中

示例代码:卖票小程序

class Ticket implements Runnable{//extends Thread{
	private int tic=100;
	public void run(){
		while(true){ 
		  if(tic>0){
	  System.out.println(Thread.currentThread().getName()+"ticket"+tic--);
			}
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		
		Ticket t=new Ticket();
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
	}
}

分析:在上述卖票程序中,如果我们按照继承Thread的方式来创建线程 那么假如有四个机器卖票,则我们需要创建四个Ticket对象,每一个对象对应一个线程

		Ticket t1=new Ticket();
		Ticket t2=new Ticket();
		Ticket t3=new Ticket();
		Ticket t4=new Ticket();
		t1.start();
		t2.start();
		t3.start();
		t4.start();

这样一来,每一个对象就会对应100张票,就会卖出400张票,这显然是不行的。而通过实现Runnable接口我们只需要创建一个Ticket对象,让它开启四个线程。这样四个线程就会共享这100张票,符合我们的需求

三、线程同步

上述例子存在问题:当0线程进入if循环之后还没有执行tic--,cpu将其执行权切换到了1线程,可能1线程进到循环之后也没有执行tic--,其执行权又被cpu切换到了线程2。我们可以在tic--该行代码前加上Thread.sleep(10),来模拟这一现象 ,结果会打印出-1,-2号票 这就是程序存在的安全隐患。

原因在于:当多条语句在操作线程共享数据时候,一个线程对多条语句值执行了一部分还没有至执行完,另一个线程进来执行,导致共享数据错误
解决办法:对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中其他线程不能进来执行,Java对于多线程的安全问题提供了专业的处理办法。就是同步代码块:

synchronized(对象)
{
需要被同步的代码
}
对象如同锁,想持有所锁的线程可以同步进行
没有锁的进程即使持有cpu的执行权也进不去,因为没有获取锁
同步的前提:
1、必须有两个或两个以上的线程
2、必须是多个线程使用同一个锁
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,比较消耗资源

上述代码可修改为:
class Ticket implements Runnable{//extends Thread{
	private int tic=100;
	
	Object obj=new Object();
	public void run(){
		while(true){ 
			synchronized (obj) {
				if(tic>0){
					try {
						Thread.sleep(10);//出现-1、-2号票 有安全问题
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"ticket"+tic--);
				}
			}
		}
	}
}

同步函数:让函数具备同步性;
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。

通过程序来验证:
一个线程在同步代码块中
一个线程在同步函数中
两个线程都在执行买票动作,如果在在同步代码块中使用obj锁就会出现0号票,如果使用this就不会出现

class Ticket2 implements Runnable {// extends Thread{
	private int tic = 100;

	Object obj = new Object();
	boolean flag = true;

	public void run() {
		if (flag) {
			while (true) {
				synchronized (this) {
					if (tic > 0) {
						try {
							Thread.sleep(10);// 出现-1、-2号票 有安全问题
						} catch (Exception e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + "obj::" + tic--);
					}
				}
			}
		} else
			while (true)
				show();
	}

	public synchronized void show() {
		if (tic > 0) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {
			}
			System.out.println(Thread.currentThread().getName() + "....show.... : " + tic--);
		}
	}
}

public class ThisLockDemo {
	public static void main(String[] args) {
		Ticket2 t = new Ticket2();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try {
			Thread.sleep(10);
		} catch (Exception e) {
		}
		t.flag = false;
		t2.start();
	}
}

注意:如果同步函数被static修饰后,使用的锁是该类对应的字节码文件对象,就是类名.class,因为静态随着类的加载而加载

死锁:同步当中嵌套着同步。而锁却不同

class DeadTest implements Runnable {
	private boolean flag;

	DeadTest(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.lacka) {
				System.out.println("if...locka");
				synchronized (MyLock.lockb) {
					System.out.println("if....lockb");
				}
			}
		} else {
			synchronized (MyLock.lockb) {
				System.out.println("else...lockb");
				synchronized (MyLock.lacka) {
					System.out.println("else....locka");
				}
			}
		}

	}

}

class MyLock {
	public static Object lacka = new Object();
	public static Object lockb = new Object();
}

public class LockDemo {
	public static void main(String[] args) {
		Thread t1 = new Thread(new DeadTest(true));
		Thread t2 = new Thread(new DeadTest(false));
		t1.start();
		t2.start();
	}
}

四、线程间通信

 线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。


示例代码:
class Res {
	String name;
	String sex;
}
class Input implements Runnable {
	private Res r;
	Input(Res r) {
		this.r = r;
	}
	@Override
	public void run() {
		int x = 0;
		while (true) {
			synchronized (r) {
				if (x == 0) {
					r.name = "mike";
					r.sex = "man";
				} else {
					r.name = "丽丽";
					r.sex = "女女女";
				}
				x = (x + 1) % 2;
			}
		}
	}
}
class Output implements Runnable {
	private Res r;
	Output(Res r) {
		this.r = r;
	}
	@Override
	public void run() {
		while (true) {
			synchronized (r) {
			System.out.println(r.name + ":::" + r.sex);
			}
		}
	}
}
public class InputOutput {
	public static void main(String[] args) {
		Res r = new Res();
		Input i = new Input(r);
		Output o = new Output(r);
		Thread t1 = new Thread(i);
		Thread t2 = new Thread(o);
		t1.start();
		t2.start();
	}
}

上述代码打印结果会出现连续的存储或者连续的取出,因此我们要加入线程的等待唤醒机制 让结果是存储一个,取出一个。上述代码可修改为

class Res {
	String name;
	String sex;
	boolean flag = false;
}

class Input implements Runnable {
	private Res r;
	Input(Res r) {
		this.r = r;
	}
	@Override
	public void run() {
		int x = 0;
		while (true) {
			synchronized (r) {
				try {r.wait();} catch (Exception e) {}
				if (x == 0) {
					r.name = "mike";
					r.sex = "man";
				} else {
					r.name = "丽丽";
					r.sex = "女女女";
				}
				x = (x + 1) % 2;
				r.flag = true;
				r.notify();
			}
		}
	}
}

class Output implements Runnable {
	private Res r;
	Output(Res r) {
		this.r = r;
	}
	@Override
	public void run() {
		while (true) {
			synchronized (r) {
				if (!r.flag)
					try {r.wait();} catch (Exception e) {}
				System.out.println(r.name + ":::" + r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}
wait:线程处于等待状态。放弃了执行权,放弃了锁。
notify():唤醒线程池中 的第一个等待线程
notifyAll():唤醒线程池中的所有等待线程。

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

生产者与消费者

class Resource{
	private String name;
	private int count=0;
	private boolean flag=false;
	
	public synchronized void set(String name){
		while(flag)
			try {this.wait();}catch(Exception e) {}
		this.name=name+"..."+count++;
		System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
		flag=true;
		this.notifyAll();
	}
	public synchronized void get(){
		while(!flag)
			try {this.wait();}catch(Exception e) {}
		System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
		flag=false;
		this.notifyAll();
	}
	
}
class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res=res;
	}
	@Override
	public void run() {
		while(true){
			res.set("商品");
		}
	}
}
class Consumer implements Runnable{
	private Resource res;
	Consumer(Resource res){
		this.res=res;
	}
	@Override
	public void run() {
		while(true){
			res.get();
		}
	}
}

public class ProducerConsumer {
	public static void main(String[] args) {
		Resource res = new Resource();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(pro);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

对于多个生产者和消费者。
要使用while循环判断标记,让被唤醒的线程再一次判断标记。不能使用if循环
要使用notifyAll(唤醒所有线程),是为了唤醒对方线程。notify,可能出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

Jdk1.5对多线程的操作进行了升级

 将同步Synchronized替换成显示Lock操作。
 将Object中的wait,notify notifyAll,替换了Condition对象。
 该对象可以通过Lock锁 进行获取。
 下面示例中,实现了本方只唤醒对方操作。

class Resource {
	private String name;
	private int count = 0;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();

	public void set(String name) throws InterruptedException {
		lock.lock();
		try {
			while (flag)
				condition_pro.await();
			this.name = name + "..." + count++;
			System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
			flag = true;
			condition_con.signal();
		} finally {
			lock.unlock();
		}
	}

	public void get() throws InterruptedException {
		lock.lock();
		try {
			while (!flag)
				condition_con.await();
			System.out.println(Thread.currentThread().getName() + "消费者" + this.name);
			flag = false;
			condition_pro.signal();
		} finally {
			lock.unlock();
		}
	}
}

class Producer implements Runnable {
	private Resource res;

	Producer(Resource res) {
		this.res = res;
	}
	@Override
	public void run() {
		while (true) {
			try {
				res.set("商品");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class Consumer implements Runnable {
	private Resource res;
	Consumer(Resource res) {
		this.res = res;
	}
	@Override
	public void run() {
		while (true) {
			try {
				res.get();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class ProducerConsumer {
	public static void main(String[] args) {
		Resource res = new Resource();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(pro);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

五、线程的其他操作

1、停止线程

stop方法已经过时。
如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();强制让冻结状态中的线程恢复到运行状态,而不是停止线程。它会获得一个中断异常。

2、守护线程
setDaemon(boolean b):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
3、加入线程
join():当A线程执行到了B线程的.join()方法时,A就会等待(冻结)。等B线程都执行完,A才会执行(恢复到运行状态)。 join可以用来临时加入线程执行。
4、更改线程优先级
setPriority(int newPriority)
public static final int MIN_PRIORITY
线程可以具有的最低优先级。(1)
public static final int NORM_PRIORITY
分配给线程的默认优先级。(5)
public static final int MAX_PRIORITY
线程可以具有的最高优先级。(10)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值