目录
基本概念
程序:是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
进程:程序的一次执行过程,或是正在执行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程。进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)
线程:进程的进一步细化为线程,是一个程序内部的一条执行路径。线程切换开销小。(线程是cpu调度的最小单位)
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU同时执行多个任务。比如:多个人做同一件事
创建的四种方式
注意,以下程序不是多线程!!
只是方法的递进调用,执行过程还是一步一步的单线程
class Untitled{
public static void main(String[] args) {
new Untitled().m2("草莓");
}
public void m2(String s){
m1(s);
}
public void m1(String s){
System.out.println(s);
}
}
继承
方式一:继承Thread类(四个步骤)
- 创建一个继承于
Thread类的子类 - 重写
Thread类的run()------>此线程要执行的操作放run()方法 - 创建
Thread类的子类的对象 - 通过此子类对象调用
Thread类的start()方法
start()方法的作用:
①启动当前线程
②调用当前线程的run()
获取当前线程名字的方法
Thread.currentThread().getName()
返回当前线程对象
Thread.currentThread()
class Untitled{
public static void main(String[] args) {
//注意一下的代码操作都是主线程执行,包括 t.start();
//创建Thread类的子类的对象
MyThread t = new MyThread();
//通过此子类对象调用Thread类的start()方法
t.start();
//直接调用run()方法,就直接是主线程执行,不是多线程
//t.run();
//主线程的方法
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
//创建一个继承于Thread类的子类
class MyThread extends Thread{
//重写Thread类的run()
public void run(){
//此线程要执行的操作放run()方法
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
再启动一个线程执行循环,就需要在创建一个Thread类的子类的对象
class Untitled{
public static void main(String[] args) {
//创建Thread类的子类的对象
MyThread t = new MyThread();
//通过此子类对象调用Thread类的start()方法
t.start();
//再启动一个线程执行循环
MyThread t1 = new MyThread();
t1.start();
}
}
//创建一个继承于Thread类的子类
class MyThread extends Thread{
//重写Thread类的run()
public void run(){
//此线程要执行的操作放run()方法
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
小练习:
创建两个分线程,其中一个线程遍历100以内的偶数,另一个遍历100以内的奇数
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.start();
MyThread2 t2 = new MyThread2();
t2.start();
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
class MyThread2 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 != 0){
System.out.println(i);
}
}
}
}
继承多窗口卖票
需求:创建3个窗口同时卖票,总票数100张。(注意:是卖同一种票,所以票是共享资源)
注意:此时这个代码还会有线程同步问题,待后期解决
class Untitled {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("A窗口");
t2.setName("B窗口");
t3.setName("C窗口");
t1.start();
t2.start();
t3.start();
}
}
class MyThread extends Thread{
//对于共享数据,需要设置成静态的,详见static关键字
public static int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(getName()+ticket);
ticket--;
}else{
break;
}
}
}
}
实现
方式二:实现Runnable类(五个步骤)
- 创建一个实现了
Runnable接口的类 - 实现类去实现
Runnable接口的run() - 创建实现类的对象
- 将此对象作为参数传递到
Thread类的构造器中,创建Thread类的对象 - 通过此
Thread类的对象调用start()方法
再启动一个线程执行循环,就只需要再创建一个Thread类的对象。不需要创建实现类的对象
class Untitled {
public static void main(String[] args) {
//创建实现类的对象
MyThread mthread = new MyThread();
//将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t = new Thread(mthread);
//通过此Thread类的对象调用start()方法
t.start();
//再启动一个线程执行循环
Thread t1 = new Thread(mthread);
t1.start();
}
}
//创建一个实现了Runable接口的类
class MyThread implements Runnable{
//实现类去实现Runable接口的run()
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
获取与更改线程的名字
因为是实现的方式创建分线程,所以实现类中不可以直接调用Thread类的方法
class Untitled {
public static void main(String[] args) {
MyThread mthread = new MyThread();
Thread t1 = new Thread(mthread);
t1.setName("线程A");
t1.start();
Thread t2 = new Thread(mthread);
t2.setName("B线程");
t2.start();
}
}
class MyThread implements Runnable{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
实现多窗口卖票
需求:创建3个窗口同时卖票,总票数100张。(注意:是卖同一种票,所以票是共享资源)
注意:此时这个代码还会有线程同步问题,待后期解决
class Untitled {
public static void main(String[] args) {
//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
MyThread mthread = new MyThread();
Thread t1 = new Thread(mthread);
t1.setName("A窗口");
t1.start();
Thread t2 = new Thread(mthread);
t2.setName("B窗口");
t2.start();
Thread t3 = new Thread(mthread);
t3.setName("C窗口");
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}else{
break;
}
}
}
}
实现Callable接口
更优处:
1、与run()方法相比,可以有返回值
2、方法可以抛出异常,这样重写的时候若是有异常,可以抛出去,也可以try-catch,要不然只能选择try-catch
3、支持泛型的返回值
4、需要借助FutureTask 类,比如获取返回值结果
FutureTask 类是Future接口的唯一实现类,同时还实现了Runnable接口。既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
Future接口可以对具体的Callable、Runnable任务的执行结果进行取消、查询是否完成、获取结果等
步骤:
1、创建一个Callable接口的实现类
2、实现call()方法,将此线程需要执行的操作声明在call()中
3、创建Callable接口实现类的对象
4、将此实现类的对象作为参数传递到FutureTask 类构造器中,创建FutureTask 类的对象
5、将FutureTask 类的对象作为参数传递到Thread类构造器中,并调用start()方法
6、获取Callable接口实现类重写的call()方法的返回值
class NumThread implements Callable{
public Object call() throws Exception{
int sum = 0;
for(int i = 0;i <= 100; i ++){
if(i % 2 == 0){
sum += i;
}
}
return sum;
}
}
public class Test{
public static void main(String[] args) {
//创建一个Callable接口的实现类对象
NumThread num = new NumThread();
//Callable实现类对象作为参数放入FutureTask构造器,创建FutureTask对象
FutureTask ft = new FutureTask(num);
//FutureTask对象作为参数放入Thread。调用start
new Thread(ft).start();
try{
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值结果
Object s = ft.get();
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}
}
}
使用线程池
JDK 5 开始提供了相关API:ExecutorService和Executors
1、 ExecutorService: 真正的线程池接口,常见子类 ThreadPoolExecutor
常见方法如下:
①void execute(Runnable command):执行任务、命令,没有返回值,一般用来执行 Runnable
②<T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行 Callable
③void shutdown():关闭线程池
2、 Executors:工具类,是线程池的工厂类,用于创建并返回不同类型的线程池
常见方法如下:
① Executors.newFixedThreadPool(int i):创建一个可重用固定线程数的线程池
② Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
③ Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
④ Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或定期地执行
class Untitled {
public static void main(String[] args) {
//1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类对象
service.execute(new Num()); //适合于Runnable
service.execute(new Num22());
//service.submit(); //适合于Callable
//3、关闭线程池
service.shutdown();
}
}
class Num implements Runnable{
public void run(){
for(int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class Num22 implements Runnable{
public void run(){
for(int i = 0; i > -10; i --){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
使用线程池的好处:
1、 提高相应速度(减少创建新线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3、便于线程管理,调用以下方法
setCorePoolSize(int i):核心池的大小
setKeepAliveTime(): 线程没有任务时最多保持多长时间后会终止
setMaximumPoolSize(): 最大线程数
class Untitled {
public static void main(String[] args) {
//1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性,先要转成子类对象,要调用子类方法
ThreadPoolExecutor s1 = (ThreadPoolExecutor)service;
s1.setCorePoolSize(15);
//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类对象
//service.execute(new Num()); //适合于Runnable
//service.execute(new Num22());
//service.submit(); //适合于Callable
//3、关闭线程池
service.shutdown();
}
}
线程常用的方法
void setName():给线程命名
String getName():返回线程的名称
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
//命名要在启动线程之前
t1.setName("线程一:");
t1.start();
//给主线程命名
Thread.currentThread().setName("主线程:");
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
}
}
}
使用有参构造器给线程命名
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
t1.start();
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
}
}
public MyThread1(String name){
super(name);
}
}
yield():线程让步,释放CPU,但其他线程能不能抢到看情况
释放了,但只释放了一点点,也可能回头自己又抢回来了CPU的资源
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
t1.start();
Thread.currentThread().setName("主线程:");
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
if(i % 20 == 0){
//释放了,但只释放了一点点,也可能回头自己又抢回来了CPU的资源
yield();
}
}
}
public MyThread1(String name){
super(name);
}
}
join():直接让调用的线程先执行(我放弃了,你先上,我礼让,你做完了我再做)
以下代码就是主线程不再执行了,先让分线程执行完之后,主线程再执行
注意需要处理异常InterruptedException,因为父类throws了异常
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
t1.start();
Thread.currentThread().setName("主线程:");
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + i);
}
if(i % 20 == 0){
//此时先让 t1 执行,执行结束之后主线程才接着执行
try{
t1.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
}
}
public MyThread1(String name){
super(name);
}
}
static void sleep(long time):让当前的线程“睡眠”time毫秒,在指定的time毫秒时间内,当前线程是阻塞状态,使其他线程有机会被执行,时间到了之后重新排队
注意需要处理异常InterruptedException,因为父类throws了异常
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
t1.start();
Thread.currentThread().setName("主线程:");
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
try{
sleep(1000); //睡眠一秒
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
}
}
public MyThread1(String name){
super(name);
}
}
boolean isAlive():返回boolean,判断线程是否还活着
class Untitled{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
t1.start();
Thread.currentThread().setName("主线程:");
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + i);
}
if(i % 20 == 0){
//此时先让 t1 执行,执行结束之后主线程才接着执行
try{
t1.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
System.out.println(t1.isAlive()); //false
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName() + i);
}
}
}
public MyThread1(String name){
super(name);
}
}
线程优先级的设置(线程的调度)
调度策略:
时间片:线程的调度采用时间片轮转的方式
抢占式:高优先级的线程抢占CPU
说明:高优先级的线程只是抢占CPU资源的概率提升,但是最终花落谁家还不一定
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 —> 默认优先级
getPriority():返回线程优先级
setPriority( int i):改变线程优先级,要在线程启动之前,里面的数可以用已有的变量
public class Test{
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程一:");
//设置分线程的优先级
t1.setPriority(MAX_PRIORITY);
t1.start();
Thread.currentThread().setName("主线程:");
//设置主线程的优先级
Thread.currentThread().setPriority(MIN_PRIORITY);
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()+":"+i);
}
}
}
}
class MyThread1 extends Thread{
public void run(){
for(int i = 0; i <= 100; i++){
if(i % 2 == 0){
System.out.println(getName()+getPriority()+":"+ i);
}
}
}
public MyThread1(String name){
super(name);
}
}
线程的生命周期
新建:当Thread类或其子类被声明并创建的时候,就是新建了线程
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,未分配到CPU资源
运行:就绪的线程被调度获得了CPU的资源,进入运行状态,分配到CPU资源
阻塞:早某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU
死亡:所有线程的最终状态只有这一个,就像人最后终有一死

线程安全问题
多个线程对账户进行操作,比如取钱的时候,刚进入账户,显示有余额100,线程阻塞了一下,此时你对象从淘宝付款也进入你的账户,并付走了100,此时你取钱的线程阻塞结束,取走了100,就形成了线程的安全问题
- 问题原因:
当某个线程操作共享数据的时候,未操作完成时,其他线程参与进来,也操作共享数据 - 问题解决:
当一个线程操作共享数据的时候,让其他线程不能参与进来,直到该线程操作结束,其他线程才可以开始操作共享数据 - 问题处理:两种方式
同步代码块
同步方法
同步代码块
synchronized(同步监视器){ //需要被同步的代码 }
好处:安全
坏处:同步的时候只有一个线程运行,其他线程等待,效率低,尽可能缩小被同步的范围
说明:
- 同步监视器:就是锁。任何一个对象都可以充当锁,但是必须
多个线程共用一把锁 - 操作共享数据的代码,就是需要被同步的代码
- 共享数据:多个线程共同操作的变量,比如:ticket
这个锁就相当于高铁公厕的厕所的提示灯,只可以有一个,若是人手一个,当你去了厕所变了红色的时候,我这边还是绿色,就会出问题
同步代码块处理实现Runable
在实现Runnable的方式中,实现类只会创建一个,所以对于多个分线程来说,这个实现类就是唯一的一份
所以在同步监视器的位置可以放this
class Untitled {
public static void main(String[] args) {
//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
MyThread mthread = new MyThread();
t1.setName("A窗口");
Thread t1 = new Thread(mthread);
t1.start();
Thread t2 = new Thread(mthread);
t2.setName("B窗口");
t2.start();
Thread t3 = new Thread(mthread);
t3.setName("C窗口");
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 100;
public void run(){
while(true){
//只有以下的语句中操作了共享数据ticket,所以把这一部分放进同步代码块
//在实现Runnable的方式中,实现类只会创建一个,所以对于多个分线程来说,这个实现类就是唯一的一份
synchronized(this){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}else{
break;
}
}
}
}
}
但是在以上代码中,若是将while(true){}语句也包括在同步代码块当中,无论执行多少次,都会出现只有一个分线程执行需要被同步的代码
此时的情况,就相当于同步方法,被同步的作用域比较大
同步代码块处理继承Thread
MyThread.class也是一个对象,而且只会加载一次
class c = MyThread.class;
class Untitled {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("A窗口");
t2.setName("B窗口");
t3.setName("C窗口");
t1.start();
t2.start();
t3.start();
}
}
class MyThread extends Thread{
//对于共享数据,需要设置成静态的,详见static关键字
public static int ticket = 100;
public void run(){
while(true){
//此时的子类会创建多个对象,所以this不是唯一的
synchronized(MyThread.class){
if(ticket > 0){
System.out.println(getName()+ticket);
ticket--;
}else{
break;
}
}
}
}
}
同步方法
同步方法就是方法的修饰词中有synchronized
如果需要同步的代码声明在一个方法中,就不妨将这个方法声明同步的
同步方法仍然涉及到同步监视器,只不过不需要我们去声明
非静态的同步方法,同步监视器:this
静态的同步方法,同步监视器:当前类本身
同步方法处理实现Runable
此时的同步监视器就是调用方法的this
class MyThread implements Runnable{
public int ticket = 100;
public void run(){
while(true){
sell();
}
}
public synchronized void sell(){ //同步监视器:this
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}
}
}
以上的同步方法和以下效果一致
public void sell(){
synchronized(this){ //同步监视器:this
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}
}
}
同步方法处理继承Thread
因为继承Thread类的方法会创建多个子类的对象,所以不能像实现Runnable接口一样直接用同步方法,需要声明成静态的,保证只有一份同步监视器
class MyThread extends Thread{
public static int ticket = 100;
public void run(){
while(true){
sell();
}
}
public static synchronized void sell(){//同步监视器:MyThread.class
if(ticket > 0){
System.out.println(getName()+ticket);
ticket--;
}
}
}
以上的同步方法和以下效果一致
public void sell(){
synchronized(MyThread.class){ //同步监视器:MyThread.class
if(ticket > 0){
System.out.println(getName()+ticket);
ticket--;
}
}
}
Lock(锁)
Lock接口是提供了对共享资源的独占访问
ReentrantLock类实现了Lock接口
ReentrantLock的构造器中可以放入boolean类型的数据
true 表示接下来的多个分线程是一个一个执行的,A、B、C、线程轮流按顺序执行锁内共享资源
false则无此规定,随机,若为空即为默认false
- 实例化ReentrantLock
- 调用锁定方法lock()
- 调用解锁方法unlock()
class Untitled {
public static void main(String[] args) {
MyThread mthread = new MyThread();
t1.setName("A窗口");
Thread t1 = new Thread(mthread);
t1.start();
Thread t2 = new Thread(mthread);
t2.setName("B窗口");
t2.start();
Thread t3 = new Thread(mthread);
t3.setName("C窗口");
t3.start();
}
}
class MyThread implements Runable{
//创建ReentrantLock的对象
private ReentrantLock lock = new ReentrantLock();
public int ticket = 100;
public void run(){
while(true){
try{
//调用锁定方法lock()
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}else{
break;
}
}finally{
//调用解锁方法unlock()
lock.unlock();
}
}
}
}
同步机制的练习题
需求:
有两个储户,向同一个账户存3000元,每次存1000元,存3次。每次存完打印账户余额
老师的答案:
//是否有多线程问题?是,两个储户线程
//是否有共享数据?有,账户
//是否有线程安全问题?有
//需要考虑如何解决线程安全问题?同步机制:三种方法
class Untitled {
public static void main(String[] args) {
Account acct = new Account();
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("储户A");
c2.setName("储户B");
c1.start();
c2.start();
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct = acct;
}
public void run(){
for(int i = 0; i < 3; i ++){
acct.deposit(1000);
}
}
}
//共同的账户
class Account{
private int money;
//存钱
public synchronized void deposit(int num){
money += num;
System.out.println(Thread.currentThread().getName()+money);
}
}
执行结果如下

使用同步代码块:
class Untitled {
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.setName("线程1:");
t2.setName("线程2:");
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
private static int money = 0;
Object obj = new Object();
public void run(){
if(money >= 0){
synchronized(obj){
for(int i = 1;i <= 3 ; i ++){
money += 1000;
System.out.println(Thread.currentThread().getName()+money);
}
}
}
}
}
执行结果如下

使用Lock(锁):
import java.util.concurrent.locks.ReentrantLock;
class Untitled {
public static void main(String[] args) {
Bank bb = new Bank();
Thread t1 = new Thread(bb);
t1.setName("储户A:");
t1.start();
Thread t2 = new Thread(bb);
t2.setName("储户B:");
t2.start();
}
}
class Bank implements Runnable{
private ReentrantLock lock = new ReentrantLock();
public static int money = 0;
public void run(){
for(int i = 1;i <= 3; i ++){
try{
lock.lock();
money += 1000;
System.out.println(Thread.currentThread().getName()+money);
}finally{
lock.unlock();
}
}
}
}
执行结果如下:

单例模式之懒汉式
懒汉式原来的有线程安全问题的代码:
class Untitled {
public static void main(String[] args) {
Test t1 = Test.m();
Test t2 = Test.m();
System.out.println(t1 == t2);
}
}
class Test{
//1/私有化构造器
private Test(){
System.out.println("a");
}
//声明 static 对象,但不创建
private static Test t = null;
//提供 public、static 的返回对象的方法
public static Test m(){
if(t == null)
t = new Test();
return t;
}
}
处理方式一,效率稍差
class Test{
private Test(){
System.out.println("a");
}
private static Test t = null;
public static Test m(){
//方式一:效率稍差
synchronized(Test.class){
if(t == null)
t = new Test();
return t;
}
}
}
处理方式二,效率稍高
当第一个线程创建了对象之后,其他分线程只需要在第一步判断的时候就会直接拿走对象,不需要拿锁在判断
class Test{
private Test(){
System.out.println("a");
}
private static Test t = null;
public static Test m(){
//方式二:效率稍高
if(t == null){
//第一步的时候,这里可能会有多个线程在等待
synchronized(Test.class){
//除开第一个分线程进去了创建对象,其他会拿锁的分线程均在此处返回
if(t == null) //注意此处不可以省略
t = new Test();
}
}
return t;
}
}
线程死锁问题
理解死锁问题
理解:
不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现提示,还是所有的线程都处于阻塞状态,无法继续
我们使用同步时,要避免出现死锁
死锁问题举例1
以下代码加上sleep()方法时候,会增大出现死锁的概率
class Untitled {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
public void run(){
synchronized(s1){
s1.append("草");
s2.append("西");
System.out.println(s1);
System.out.println(s2);
//调用sleep()方法,增大死锁概率
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
}
synchronized(s1){
s1.append("111111");
s2.append("222222");
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(new Runnable(){
public void run(){
synchronized(s1){
s1.append("莓");
s2.append("瓜");
System.out.println(s1);
System.out.println(s2);
//调用sleep()方法,增大死锁概率
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
}
synchronized(s1){
s1.append("333333");
s2.append("444444");
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
死锁问题举例2
class A{
public synchronized void foo(B b){
System.out.println(Thread.currentThread().getName()+"进入A类的foo方法");
//调用sleep()方法,增大死锁概率
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"企图调用B类的last方法");
b.last();
}
public synchronized void last(){
System.out.println("这里是A类的last方法");
}
}
class B{
public synchronized void bar(A a){
System.out.println(Thread.currentThread().getName()+"进入B类的bar方法");
//调用sleep()方法,增大死锁概率
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"企图调用A类的foo方法");
a.last();
}
public synchronized void last(){
System.out.println("这里是B类的last方法");
}
}
public class Test implements Runnable{
A a = new A();
B b = new B();
public void init(){
Thread.currentThread().setName("主线程");
a.foo();
System.out.println("进入了主线程之后");
}
public void run(){
Thread.currentThread().setName("分线程");
b.bar(a);
System.out.println("进入了分线程之后");
}
public static void main(String[] args) {
Test t1 = new Test();
new Thread(t1).start();
t1.init();
}
}
线程的通信
比如两个线程交替打印,这个交替过程就是线程间的通信
举例:
使用两个线程打印1-100,交替打印
wait():一旦执行,当前线程进入阻塞状态,并释放同步监视器!!!
notify():一旦执行,会唤醒被wait()的随机一个线程
notifyAll():一旦执行,会唤醒被wait()的所有线程
说明:
1、以上三个方法只可以使用在同步代码块或同步方法中
2、以上三个方法的调用者必须是同步监视器,否则会报异常
3、以上三个方法是定义在 java.lang.Object类中的,因为同步监视器可以使任何一个对象,因为可以被如何一个对象调用,所以是Object的方法
class Untitled {
public static void main(String[] args) {
//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
MyThread mthread = new MyThread();
t1.setName("A窗口");
Thread t1 = new Thread(mthread);
t1.start();
Thread t2 = new Thread(mthread);
t2.setName("B窗口");
t2.start();
Thread t3 = new Thread(mthread);
t3.setName("C窗口");
t3.start();
}
}
class MyThread implements Runnable{
public int num = 1;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
//让调用notify()方法以外的随机一个在wait()的线程苏醒
obj.notify();
if(num <= 100){
System.out.println(Thread.currentThread().getName()+num);
num++;
//让调用wait()方法的线程进入阻塞状态
try{
obj.wait();
}catch(InterruptException e){
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
线程通信练习题
需求:
生产者(Productor)将产品交给店员(Clerk)。消费者(Customer)从店员处拿走产品。
店员一次只可以持有 20 的产品,如果店中产品满了,店员会让生产者停一下,等店中有空位再让生产者生产。如果店中没有产品了,店员会让消费者等一下,等店中有产品了再通知消费者拿走产品
注意: 生产者比消费者快的时候,消费者可能会漏掉一些数据没有取到。消费者比生产者快的时候,消费者会取相同的数据
小结
- 理解程序、进程、线程的概念
- 多线程创建的两个方式
- 方式一:继承
Thread类(四个步骤)- 创建一个继承于
Thread类的子类 - 重写
Thread类的run()
此线程要执行的操作放run()方法 - 创建
Thread类的子类的对象 - 通过此子类对象调用
Thread类的start()方法
- 创建一个继承于
- 方式二:实现
Runnable接口(五个步骤)- 创建一个实现了
Runnable接口的类 - 实现类去实现
Runnable接口的run() - 创建实现类的对象
- 将此对象作为参数传递到
Thread类的构造器中,创建Thread类的对象 - 通过此
Thread类的对象调用start()方法
- 创建一个实现了
- 比较两种创建线程的方式:
- 优先选择实现
Runable接口的方式
因为实现的方式没有类的单继承的局限性
实现的方式更适合来处理多个线程有共享数据的情况(比如多窗口卖票情况) - 联系:实际上
Thread类也是实现了Runable接口 - 相同点:两种方式都要重写
run()方法,将线程要执行的逻辑放入当中
- 优先选择实现
- 方式三:实现
Callable接口(六个步骤)- 创建一个
Callable接口的实现类 - 实现
call()方法,将此线程需要执行的操作声明在call()中 - 创建
Callable接口实现类的对象 - 将此实现类的对象作为参数传递到
FutureTask类构造器中,创建FutureTask类的对象 - 将
FutureTask类的对象作为参数传递到Thread类构造器中,并调用start()方法 - 获取
Callable接口实现类重写的call()方法的返回值
- 创建一个
- 实现
Callable接口创建线程的优处:
可以有返回值、支持泛型的返回值、可以抛异常 - 方式四:使用线程池
start()方法的作用:
①使当前线程变为可运行状态(CPU调资源执行)
②调用当前线程的run()- 线程的常用方法
void start():启动线程,并执行对象的run()方法run():线程在被调用是执行的操作String getName():返回线程的名称void setName():给线程命名,也可以通过有参构造器命名static Thread currentThread():返回当前线程对象,在Thread子类中就是this,通常用于主线程和Runnable实现类yield():线程让步,释放当前CPU的执行权,但其他线程能不能抢得到看情况(我停了一步,给你机会了,看你把不把握得住)join():线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态(我放弃了,你先上,我礼让,你做完了我再做)
注意需要处理异常InterruptedException
static void sleep(long l):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到了之后重新排队
注意需要处理异常InterruptedExceptionboolean isAlive():返回boolean,判断线程是否还活着stop():已过时。强制线程生命期结束getPriority():返回线程优先级setPriority( int i):改变线程优先级- 线程优先级的设置:高优先级的线程只是抢占CPU资源的概率提升,但是最终花落谁家还不一定
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 - 线程的生命周期
- 线程同步的两个处理方式
- 方式一:同步代码块
synchronized(同步监视器){ //需要被同步的代码 }- 同步监视器:就是锁。任何一个对象都可以充当锁,但是必须
多个线程共用一把锁
① 在实现Runnable接口的方法中,可以考虑用this充当同步监视器
②在继承Thread类的方法中,慎用this充当,可以考虑用当前类充当 - 操作共享数据的代码,就是需要被同步的代码
- 共享数据:多个线程共同操作的变量,比如:ticket
- 同步监视器:就是锁。任何一个对象都可以充当锁,但是必须
- 方式二:同步方法
- 同步方法仍然涉及到同步监视器,只不过不需要我们去声明
- 非静态的同步方法,同步监视器:this
- 静态的同步方法,同步监视器:当前类本身
- 方式三:Lock(锁)
- 实例化
ReentrantLock - 调用锁定方法
lock()与解锁方法unlock()
- 实例化
synchronized与Lock的异同Lock是显式锁,需要手动开启和关闭锁。synchronized是隐式锁,除了作用域自动释放Lock只有代码块锁,synchronized有代码块锁和方法锁- 使用
Lock,JVM可以花费更少的时间调度线程,性能更好 - 优先使用顺序:
Lock—同步代码块—同步方法
- 方式一:同步代码块
- 单例模式之懒汉式
直接把提供对象的方法定义为同步方法,如下:
提供public、static、synchronized的返回对象的方法 - 线程死锁问题
不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
两个人需要走两步,A已经走第一步拿了锁1,现在走第二步需要锁2,可是B已经走了第一步拿了锁2,现在走第二步需要锁1 。二人互不相让,就死锁了 - 线程的通信
- 三个方法:
wait():一旦执行,当前线程进入阻塞状态,并释放同步监视器!!!
notify():一旦执行,会唤醒被wait()的随机一个线程
notifyAll():一旦执行,会唤醒被wait()的所有线程 - 注意:
- 三个方法只可以使用在同步代码块或同步方法中
- 三个方法的调用者必须是同步监视器,否则会报异常
- 三个方法是定义在
java.lang.Object类中的,因为同步监视器可以使任何一个对象,因为可以被如何一个对象调用,所以是Object的方法
- 三个方法:
wait()与sleep()的异同:- 相同点:一旦执行,都可以让当前线程进入阻塞状态
- 不同点:
①声明位置不同
sleep()是Thread类的静态方法,wait()是Object类的非静态方法
②调用要求不同
sleep()可以任何场景使用,wait()需要在同步代码块或同步方法中用
③释放同步监视器问题
wait()在阻塞的同时会释放同步监视器,sleep()不会,仅仅是阻塞
10万+

被折叠的 条评论
为什么被折叠?



