java 线程个人笔记

1、简介

都知道,比如Java做那种卖票的,比如卖10张票,如果没加锁,那么每一个线程都是卖10张票,这不合理

2、错误示范

多线程执行同一个东西,没加锁,就有有错误

package restudy;

public class MyRunnable implements Runnable {

	int ticket = 20;
	
	
	public void run() {
		while (true) {
			sale();

		}
	}

	public  void sale() {
		if (ticket > 0) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "------" + ticket);
			ticket--;
		}
		
	}
}

package restudy;


public class demo1 {

	public static void main(String[] args) {

		MyRunnable run = new MyRunnable();
		Thread mt1 = new Thread(run);
		Thread mt2 = new Thread(run);
		Thread mt3 = new Thread(run);

		mt1.start();
		mt2.start();
		mt3.start();
	}

}

Thread-1------20
Thread-0------20
Thread-2------18
Thread-0------17
Thread-1------17
Thread-2------17
Thread-1------14
Thread-2------14
Thread-0------14
Thread-1------11
Thread-2------11
Thread-0------11
Thread-2------8
Thread-1------8
Thread-0------8
Thread-1------5
Thread-2------5
Thread-0------5
Thread-0------2
Thread-1------2
Thread-2------2
Thread-1-------1

这很明显是不合理的,有些这个线程执行过了,那个线程还执行一遍

3、同步代码块

  • 同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。 格式:
synchronized(同步锁){
 	需要同步操作的代码 
 }
  • 注意:

    1. 通过代码块中的锁对象,可以使用任意的对象
    2. 但是必须保证多个线程使用的锁对象是同一个
    3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
  • 思路:一个线程里面,初定义一个 对象,可以是任意对象,用synchronized锁住该对象,这样多个线程一起执行的时候,就会用到同一个对象锁住

package restudy;

public class MyRunnable implements Runnable {

	int ticket = 20;
	Object lock = new Object();

	public void run() {
		while (true) {
			//所住一个方法
			synchronized (lock) {
				sale();
			}

		}
	}

	public void sale() {
		if (ticket > 0) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "------" + ticket);
			ticket--;
		}

	}
}

package restudy;

public class demo1 {

	public static void main(String[] args) {

		MyRunnable run = new MyRunnable();
		Thread mt1 = new Thread(run);
		Thread mt2 = new Thread(run);
		Thread mt3 = new Thread(run);

		mt1.start();
		mt2.start();
		mt3.start();
	}

}

Thread-0------20
Thread-2------19
Thread-2------18
Thread-1------17
Thread-2------16
Thread-0------15
Thread-2------14
Thread-1------13
Thread-1------12
Thread-2------11
Thread-2------10
Thread-0------9
Thread-2------8
Thread-1------7
Thread-2------6
Thread-0------5
Thread-2------4
Thread-1------3
Thread-2------2
Thread-0------1

4、同步方法

public synchronized void 方法名(){
	可能会产生线程安全问题的代码
}
package restudy;

public class MyRunnable implements Runnable {

	int ticket = 20;
	
	
	public void run() {
		while (true) {
			sale();

		}
	}
	//方法添加锁
	public synchronized void sale() {
		if (ticket > 0) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "------" + ticket);
			ticket--;
		}
		
	}
}

package restudy;


public class demo1 {

	public static void main(String[] args) {

		MyRunnable run = new MyRunnable();
		Thread mt1 = new Thread(run);
		Thread mt2 = new Thread(run);
		Thread mt3 = new Thread(run);

		mt1.start();
		mt2.start();
		mt3.start();
	}

}

Thread-0------20
Thread-0------19
Thread-2------18
Thread-1------17
Thread-2------16
Thread-0------15
Thread-2------14
Thread-2------13
Thread-1------12
Thread-2------11
Thread-0------10
Thread-2------9
Thread-1------8
Thread-2------7
Thread-0------6
Thread-2------5
Thread-1------4
Thread-2------3
Thread-0------2
Thread-2------1

5、Lock锁

使用步骤:

  • 在成员位置中创建一个ReentrantLock对象
  • 在可能会出现线程安全问题的代码调用lock()方法
  • 在可能会出现线程安全问题的代码调用unlock()方法
package restudy;

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


public class MyRunnable implements Runnable {

	int ticket = 20;
	Lock l=new ReentrantLock();//创建Lock对象

	public void run() {
		while (true) {
			l.lock();//加锁
			sale();//执行代码
			l.unlock();//释放锁
		}
	}

	public void sale() {
		if (ticket > 0) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "------" + ticket);
			ticket--;
		}

	}
}

package restudy;


public class demo1 {

	public static void main(String[] args) {

		MyRunnable run = new MyRunnable();
		Thread mt1 = new Thread(run);
		Thread mt2 = new Thread(run);
		Thread mt3 = new Thread(run);

		mt1.start();
		mt2.start();
		mt3.start();
	}

}

Thread-0------20
Thread-1------19
Thread-2------18
Thread-0------17
Thread-1------16
Thread-2------15
Thread-0------14
Thread-1------13
Thread-2------12
Thread-0------11
Thread-1------10
Thread-2------9
Thread-0------8
Thread-0------7
Thread-1------6
Thread-2------5
Thread-0------4
Thread-1------3
Thread-2------2
Thread-0------1

6、等待唤醒机制(个人还不是很会)

上面的那些,都是几个线程之间互相争夺CPU线程,谁抢到谁运行的,这类似于斗争,但除了这种情况,还有合作的机制。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

方法:

wait/notify 就是线程间的一种协作机制。

  • wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时 的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象 上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中。
  • notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先 入座。
  • notifyAll:则释放所通知对象的 wait set 上的全部线程。
细节:
  • wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。
  • wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。
  • wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方 法。
  • 虽说是不争系统CPU,但还是要在可能发生线程安全问题的地方使用synchronized

1)不同类的休眠唤醒

例子:创建一个
ObjectTest类 ObjectTest类,属性有flag。当flag为假时,WaitTest休眠,等待NotifyTes将其唤醒。当当flag为真时,NotifyTes休眠,等待WaitTest将其唤醒

package restudy;

public class ObjectTest {
	//假设我这个一开始就设定为假的了,这个一开始要有值,赋值的形式很多就不细说了
	boolean flag=false;
}

WaitTest类

package restudy;

public class WaitTest extends Thread {
	private ObjectTest obj;

	public WaitTest(ObjectTest obj) {
		super();
		this.obj = obj;
	}

	@Override
	public void run() {
		// 不断循环
		while (true) {
			// 这里为什么要加锁并且锁obj?
			// 因为一开始说了,休眠与唤醒必须是同一个对象,那么WaitTest类和NotifyTest类必须要用同一个对象,这个对象就是ObjectTest
			synchronized (obj) {
				// 当ObjectTest对象为false时,WaitTest进行休眠
				if (obj.flag == false) {
					System.out.println(this.getClass().getName() + " " + "准备开始休眠");
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}

				}
				// 一下代码是唤醒的时候执行的
				// 当代码执行到这里的时候,ObjectTest已经被唤醒了
				else  {
					System.out.println(this.getClass().getName() + " " + "被唤醒");
					obj.flag = false;
					obj.notify();
				}
				
			}

		}
	}
}

NotifyTest类

package restudy;

public class NotifyTest extends Thread {
	private ObjectTest obj;

	public NotifyTest(ObjectTest obj) {
		super();
		this.obj = obj;
	}

	@Override
	public void run() {
		// 不断循环
		while (true) {
			synchronized (obj) {
				if (obj.flag == true) {
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
				}
				
				//代码执行到这里的时候,说明flag=false并且准备讲WaitTest唤醒
				//WaitTest休眠了,NotifyTest就趁虚而入
				
				
				else  {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
					System.out.println(this.getClass().getName() + "准备唤醒另一个程序");
					obj.flag = true;
					obj.notify();
				}
				
			}
		}
	}
}

主程序

package restudy;


public class demo1 {

	public static void main(String[] args) {

		ObjectTest obj=new ObjectTest();
		WaitTest w=new WaitTest(obj);
		NotifyTest n=new NotifyTest(obj);
		
		w.start();
		n.start();
	}

}

代码执行流程:
主程序,创建3个类,然后start,。首先,走到WaitTest对象,锁obj,判断obj.flag为false,输出准备开始休眠WaitTest进行了休眠,这个时候WaitTest不再争夺CPU,由于两个类都是不断循环的,WaitTest休眠了,NotifyTest乘虚而入,判断flag为flase,不符合NotifyTest的休眠条件,NotifyTest进行语句,其从29行开始,睡眠0.5秒,输出准备唤醒另一个程序,改变flag的值为true,obj.notify();

注意NotifyTest的38行obj.notify();,唤醒有用到obj的程序,WaitTest唤醒了。这时候flag==true

两个程序判断flag值,NotifyTest休眠,WaitTest判断值不为flag,进行31行的代码,输出被唤醒,将flag变为false,唤醒有用到obj的程序,NotifyTest醒了

又是一次新的轮回,这时候flag为false,WaitTest休眠,NotifyTest活动…

7、线程状态

java 线程个人笔记_第1张图片

8、线程池

从上面的例子看到,每要用一个线程就要创建一个线程,这在开发中是很慢的,就产生了一个叫线程池的东西

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。

一开始就创建好线程池,要用就拿出来,用完了(run()完)不要释放,丢回去线程池,所以线程池是一直存在的

1)线程池的好处:
  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2)常用方法
  1. public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)。返回值:ExecutorService接口
  2. Future submit(Runnable task):提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null。 参数:task - 要提交的任务。返回:表示任务等待完成的 Future
3)步骤
  1. 创建线程池对象(Executors.newFixedThreadPool(int 几个线程容量)),返回集为ExecutorService
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  3. 调用ExecutorService的submi()方法,传递线程任务,开启线程,执行run方法
  4. 关闭线程池(一般不做)(shutdown())。
4)例子
package restudy;

public class RunnableImpl implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "创建了一个新的线程");

	}

}

package restudy;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo1 {

	public static void main(String[] args) {
		// 创建一个容量为2的线程池,返回为ExecutorService
		ExecutorService ex = Executors.newFixedThreadPool(2);
		//传递线程任务,开启线程,执行run方法
		ex.submit(new RunnableImpl());
		ex.submit(new RunnableImpl());
		ex.submit(new RunnableImpl());
		ex.submit(new RunnableImpl());
	}

}

pool-1-thread-2创建了一个新的线程
pool-1-thread-2创建了一个新的线程
pool-1-thread-1创建了一个新的线程
pool-1-thread-2创建了一个新的线程

第一次ex.submit(new RunnableImpl());,用了线程池里面的随机一个线程来执行,run()只包含了一个输出语句,run完后就结束了,结束之后就把该线程丢回线程池,等待下一次线程的产生。第二次submit,又随机调用一个线程池里面的线程来进行run()…

你可能感兴趣的:(java)