JAVA SE 高级知识学习笔记( 多线程)

 多线程

* 1.什么是线程
    * 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    * 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
* 2.多线程的应用场景
    * QQ同时和多个人一起视频
    * 服务器同时处理多个客户端请求

多线程并行和并发的区别

* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
* 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
* 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

 

1、 多线程程序实现

1.1、 Thread类实现多线程

 * 多线程的实现方式一(首选)
 * 1、自定义类继承Thread类
 * 2、在自定义类中重写run方法 将要执行的代码写在里面
 * 3、主方法中实例化自定义类对象
 * 4、调用start();方法开启多线程


public class Demo01_Thread {
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		mt.start();
		
		for(int i = 0;i < 1000;i++)
			System.out.println("0000000000000000000");
	}
}
class MyThread extends Thread{
	public void run() {
		for(int i = 0;i < 1000;i++)
			System.out.println("---------------------");
	}
}

 

1.2、 Runnable 接口实现多线程

* 多线程的实现方式二
 * 1、自定义类 实现 Runnable 接口
 * 2、在自定义类中重写run方法 将要执行的代码写在里面
 * 3、主方法中实例化自定义类对象 
 * 4、创建线程对象Thread,将自定义类对象当作参数传给Thread
 * 5、调用start();方法开启多线程

public class Demo02_Runnable {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyRunnable mt = new MyRunnable();
		Thread t = new Thread(mt);
		t.start();
		
		for(int i = 0;i < 1000;i++)
			System.out.println("66666666666666666666");
	}

}
class MyRunnable implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for( int i = 0;i < 1000;i++ )
			System.out.println("-----------------------");
	}
	
}

 

1.3、 匿名内部类实现线程的两种方式

public class Demo03 {
	public static void main(String[] args) {
		new Thread() {	//1、匿名类继承Thread方法
			public void run() {
				//2、重写run方法
			}
		}.start();//3、调用start方法
		
		/** ****************************************/
		
		new Thread(new Runnable() {
			public void run() {
				
			}
		}).start();
		
	}
}

 

2、 多线程 获取名字和设置名字

2.1、 Thread类实现获取名字和设置名字

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//父类引用指向子类对象
		Thread t1 = new Thread() {
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(this.getName() + "....aaaaa");	//获取该线程的名字
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(this.getName() + "....111111");	//获取该线程的名字
			}
		};
		t1.setName("第一个线程的名字");  //或者在Thread("第一个线程的名字")效果一样
		t2.setName("第二个线程的名字");
		
		t1.start();
		t2.start();
	}

 

2.2、 Runnabel接口 实现获取名字和设置名字

	//cuurentThread()方法 返回当前正在执行的线程对象的引用   返回类型Thread
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//new Thread(Runnable的子类对象).start();
		Thread t1 = new Thread(new Runnable() {
			//本类引用指向本类对象
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(Thread.currentThread().getName() +  "....aaaaa");	//获取该线程的名字
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(Thread.currentThread().getName() +  "....000000");	//获取该线程的名字
			}
		});
		
		t1.setName("第一个线程");
		t2.setName("第二个线程");
		
		t1.start();
		t2.start();
	}

 

 3、 休眠线程

Thread.sleep(1000);    //停留一秒

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		for(int i = 1;i < 20;i++) {
			Thread.sleep(1000);//停留一秒
			System.out.println("这是第" + i + "秒");
		}
			
	}

 

4、 守护线程

守护线程:设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

t1.setDaemon(true);        //设置t1为守护线程,则t2线程执行完成后 t1自动结束

	public static void main(String[] args) {
		Thread t1 = new Thread() {
			public void run() {
				for(int i = 0;i < 1000;i++ )
					System.out.println(getName() + "....aaaa");
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0;i < 2;i++)
				System.out.println(getName() + "....1111");
			}
		};
		
		t1.setDaemon(true);		//设置t1为守护线程,则t2线程执行完成后 t1自动结束
		t1.start();
		t2.start();	
	}

 

5、 加入线程(插队线程)

* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
* join(int), 可以等待指定的毫秒之后继续

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Thread t1 = new Thread() {
			public void run() {
				for(int i = 0;i < 100;i++ )
					System.out.println(getName() + "....aaaa");
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				//父类方法没有抛异常 则子类方法也不能抛异常 只能在子类方法里面处理异常
				for(int i = 0;i < 100;i++) {
					if( i==2 ) {
						//当i等于二时 则t2线程暂停执行,等t1线程全部执行完成后才可以继续执行t2线程,相当于提升了t2线程的级别
						try {
							t1.join();	//匿名内部类在使用当前方法的局部变量时 必须用final修饰
							// t1.join(30);  当i等于二时 则t2线程暂停执行30ms 之后与t1交替执行
						
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					System.out.println(getName() + "....1111");
				}
			}
		};
		
		t1.setDaemon(true);		//设置t1为守护线程,则t2线程执行完成后 t1自动结束
		t1.start();
		t2.start();	
	}

5、 同步代码块 重要

* 1.什么情况下需要同步
   当多线程并发(交替进行), 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
   如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
* 2.同步代码块
    * 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块(任意对象都可以当做锁对象,但是匿名对象不可以当做锁对象
    * 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

/*
 * 同步代码块
 * 当多线程并发(交替进行), 有多段代码同时执行时,
 * 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作
 *  这时就需要同步
 */
public class Demo09_synchronized {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Printer p = new Printer();
		new Thread() {
			public void run() {
				for(int i = 0;i < 1000;i++)
					p.print1();
			}
		}.start();
		
		new Thread() {
			public void run() {
				for(int i = 0;i < 1000;i++)
					p.print2();
			}
		}.start();
	}

}
class Printer{
	Object obj = new Object();
	public void print1() {
		synchronized(obj) {	
			/*
			 * 相当于锁,只有当下面代码全部执行完成 其它线程的代码
			 * 才可以继续执行
			 * 任意对象都可以当做锁对象,但是匿名对象不可以当做锁对象
			 * 被锁的代码需要保证是同一把锁
			 */
			System.out.print("J");
			System.out.print("A");
			System.out.print("V");
			System.out.print("A");
			System.out.print("\r\n");
		}
	}
	
	public void print2() {
		synchronized(obj) {
			System.out.print("C");
			System.out.print("+");
			System.out.print("+");
			System.out.print("\r\n");
		}
	}
	
}

6、 同步方法 重要

使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

/*
 * 同步方法
 * 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
 * 同步非静态方法默认锁为this
 * 同步静态方法默认锁为 该类的字节码对象(Printer.class)
 */
public class Demo10_synchronizeed_2 {
	public static void main(String[] args) {
		Print p = new Print();
		new Thread() {
			public void run() {
				for(int i = 0;i < 1000;i++)
					p.demo01();
			}
		}.start();
		new Thread() {
			public void run() {
				for(int i = 0;i < 1000;i++)
					p.demo02();
			}
		}.start();
		
	}
}
class Print{
	public synchronized void demo01() {
		//同步非静态方法默认锁为this
		System.out.print("J");
		System.out.print("A");
		System.out.print("V");
		System.out.print("A");
		System.out.print("\r\n");
	}
	
	public synchronized void demo02() {
		System.out.print("C");
		System.out.print("+");
		System.out.print("+");
		System.out.print("\r\n");
	}
	
}

7、线程安全问题

* 多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

例子:火车售票 4个窗口卖100张票 Thread类实现

/*
 * 线程安全问题
 * 模拟铁路售票,4个窗口卖100张
 * Thread类实现
 */
public class Demo11_Ticket {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Ticket("1号窗口").start();		//会报错 需重写Ticket类构造方法
		new Ticket("2号窗口").start();
		new Ticket("3号窗口").start();
		new Ticket("4号窗口").start();
	}

}
class Ticket extends Thread{
	public Ticket(String name) {
		super(name);
	}
	private static int T = 100;		//100张票	需要定义为static类型才可以使得4个对象共用100这个数
	public void run() {
		while(true) {
			synchronized(Ticket.class) {	
				//需要加锁,如果不加会出现负票 
				//此时锁的对象不能是this 因为有四个不同的对象 只能用该类的字节码文件
				//(不加锁线程1234睡,中途线程1醒,T--后小于0。 此时线程2醒T还会继续--) 
				if(T <= 0)
					break;
				try {
				Thread.sleep(10);	//线程1 2 3 4睡
				} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("这是第" + getName() + "....." + T-- + "张票");
			
			}
		}
			
	}
	
}

例子:火车售票 4个窗口卖100张票 Runnable 接口实现

/*
 * 线程安全问题
 * 模拟铁路售票,4个窗口卖100张
 * Runnable 接口实现
 */
public class Demo12_TicketRunnable {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TicketR t = new TicketR();
		
		//第一种方式开启四条线程
//		Thread t1 = new Thread(t,"1 号窗口");
//		Thread t2 = new Thread(t,"2 号窗口");
//		Thread t3 = new Thread(t,"3 号窗口");
//		Thread t4 = new Thread(t,"4 号窗口");
//		
//		t1.start();
//		//t1.start();	多次启动一条线程是非法的
//		t2.start();
//		t3.start();
//		t4.start();
		
		
		// 第二种方式开启四条线程
		new Thread(t,"窗口1").start();
		new Thread(t,"窗口2").start();
		new Thread(t,"窗口3").start();
		new Thread(t,"窗口4").start();
		
	}

}
class TicketR implements Runnable{
	private int T = 1000;
	public void run() {
		while(true) {
			synchronized(TicketR.class) {
			if(T <= 0)
				break;
			try {
				Thread.sleep(10);
			}catch(Exception e) {
				e.printStackTrace();
			}
			System.out.println("这是第" + Thread.currentThread().getName() +"....." + T-- + "张票");
			}
		}
	}
	
}

* 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁。尽量不要嵌套使用

 

8、礼让线程 (了解 支持的不好)

一个线程在执行过程中满足某个条件调用Thread.yeield() 则该线程等待其他线程开始 yield让出cpu

/*
 * 礼让线程 (了解 支持的不好)
 * yieid()
 */
public class Demo01_Yieid {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new MyThread().start();	//默认为Thread-0
		new MyThread().start();	//Thread-1
	}

}
class MyThread extends Thread {
	public void run() {
		for(int i = 0;i <1000;i++) {
			if( i == 100)
				Thread.yield();
		System.out.println(getName() + "........" + i);
		}
	}
}

9、设置线程的优先级

 setPriority() 设置线程优先级  ,最高级是10 (MAX_PRIORITY), 最低级是1    MIN_PRIORITY。默认是5

public class Demo02_Priority {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread t1 = new Thread() {
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(getName() + "........a");
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for( int i = 0;i < 1000;i++ )
					System.out.println(getName() + "........bbbbbbbb");
			}
		};
		
		
		t1.setPriority(Thread.MAX_PRIORITY);	//设置t1为最优先级
		t2.setPriority(Thread.MIN_PRIORITY);
		
		t1.start();
		t2.start();
		
	}

}

 

10、单例设计模式

* 单例设计模式:保证类在内存中只有一个对象。
 * 饿汉式 懒汉式区别:
 * 如果都是单线程  饿汉式:空间换时间 懒汉式:时间换空间
 * 如果都是多线程  饿汉式不会出现安全隐患,懒汉式会出现多个对象的安全隐患

  如何保证类在内存中只有一个对象呢?
    * (1)控制类的创建,不让其他类来创建本类的对象。private构造函数
    * (2)在本类中定义一个本类的对象。Singleton s;
    * (3)提供公共的访问方式。  public static Singleton getInstance(){return s}

public class Demo03_Singleton {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Singleton s1 = Singleton.getInstance();	//s1和s指向的是一个对象,它俩地址是一样的
		Singleton s2 = Singleton.getInstance();	//s2和s指向的也是一个对象
		 
		System.out.println(s1 == s2);		//true
		
	}

}
//	饿汉式 (先创建对象)
//class Singleton{
//	//1 、 私有构造 不让其他类创建本类对象
//	private Singleton() {}
//	//2 、 本类创建本类对象
//	private static Singleton s = new Singleton();
//	//3、 对外提供公共的访问方法,返回本类对象
//	public static Singleton getInstance() {
//		return s;
//	}
//	
//}

// 懒汉式,单例的延迟加载  (什么时候用什么时候创建对象)

class Singleton{
	private Singleton() {}
	
	private static Singleton s;
	
	public static Singleton getInstance() {
		if (s==null)
			s = new Singleton();
		return s;
	}
}

 

11、Runtime类 Timer类

Runtime类是一个单例类   Timer类:计时器

public class Demo04_Runtime {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//Runtime r = new Runtime();	错误写法,因为构造方法已经被私有化
		Runtime r = Runtime.getRuntime();	//获取到Runtime类的实例化对象
		r.exec("要执行的cmd命令");
	
	}

}

Timer类在指定时间执行指定任务

public class Demo05_Timer {
	public static void main(String[] args) {
		//创建计时器对象
			Timer t = new Timer();
		//安排指定时间执行MyTimerTast类下的Run方法下指定任务
		t.schedule(new MyTimerTast(), 2000);
		//2s后输出背单词
	}
}

class MyTimerTast extends TimerTask{
	public void run() {
		//重写run方法
		System.out.println("背单词");
	}
}

 

12、两个线程间的通信 (掌握)

1.什么时候需要通信
    * 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    * 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
* 2.怎么通信
    * 如果希望线程等待, 就调用wait()
    * 如果希望唤醒等待的线程, 就调用notify(); notify()方法是随机唤醒一个线程
    * 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

public class Demo06_Notify {
	public static void main(String[] args) {
		Printer p = new Printer();
		//创建第一条线程
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		//创建第二条线程
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		
	}
}
class Printer{
	private int num = 1;
	public void print1() throws InterruptedException {
		synchronized(this) {
			if( num!=1 )
				this.wait();
			System.out.print("J");
			System.out.print("A");
			System.out.print("V");
			System.out.print("A");
			System.out.print("\r\n");
			num = 2;
			this.notify();
		}
	}
	
	public void print2() throws InterruptedException {
		synchronized(this) {
			if( num!=2 )
				this.wait();	//如果没有notify解锁此线程将一直等待
			System.out.print("C");
			System.out.print("+");
			System.out.print("+");
			System.out.print("\r\n");
			num = 1;
			this.notify();
		}
	}
}

 

13、多线程互斥锁(n个线程间的通信)

 * 互斥锁
 * 1、同步
 * 使用ReentrantLock类的lock()和unlock()方法进行同步
 * 2、通信
 * 使用ReentrantLock类的newCondition()方法可以获取Condition对象
 * 需要等待的时候使用Condition的await()方法,唤醒时候用signal()方法
 * 不同的线程使用不同的Condition,这样就能区分唤醒的是哪个线程了

eg:3个线程间的通信

public class Demo07_ReentrantLock {

	public static void main(String[] args) {
		
		Print p = new Print();
		//创建三个线程 分别调用子类的三个方法
		new Thread() {
			public void run() {
				try {
					while(true) {
						p.print1();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				try {
					while(true) {
						p.print2();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				try {
					while(true) {
						p.print3();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
		
		
	}

}
class Print{
	ReentrantLock r = new ReentrantLock();	//创建ReentrantLock类
	//获取Condition对象	因为有不同的线程需要使用不同的Condition  所以需要创建三个对象 
	Condition c1 = r.newCondition();		
	Condition c2 = r.newCondition();	
	Condition c3 = r.newCondition();	
	
	private int num = 1;
	public void print1() throws InterruptedException {
		r.lock();		//互斥锁
			while(num != 1) {
				c1.await();	//c1等待
			}	
			System.out.print("J");
			System.out.print("A");
			System.out.print("V");
			System.out.print("A");
			System.out.print("\r\n");
			num = 2;
			c2.signal(); 	//把c2叫醒
		r.unlock();	//解锁
	}
	public void print2() throws InterruptedException {
		r.lock();		//互斥锁
			while(num != 2) {
				c2.await();	//c2等待
			}
			System.out.print("C");
			System.out.print("+");
			System.out.print("+");
			System.out.print("\r\n");
			num = 3;
			c3.signal();	//把c3叫醒
		r.unlock();
	}
	
	public void print3() throws InterruptedException {
		r.lock();
			while(num != 3) {
				c3.await();		//c3等待
			}
			System.out.print("H");
			System.out.print("T");
			System.out.print("M");
			System.out.print("L");
			System.out.print("\r\n");
			num = 1;
			c1.signal();	//把c1唤醒
		r.unlock();
	}
	
}

14、 线程组 ThreadGroup

 * Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
 * 默认情况下,所有的线程都属于主线程组。

public class Demo08_ThreadGroup {

	public static void main(String[] args) {
		ThreadGroup tg = new ThreadGroup("我是一个线程组");	//创建一个线程组
		// 参数  ThreadGroup对象 、MyRunnable对象 、线程的名字 
		Thread t1 = new Thread( tg, new MyRunnable(), "张三" );	//将指定线程加入指定组
		Thread t2 = new Thread( tg, new MyRunnable(), "李四" );
		//对整组进行设置
		//tg.setDaemon(true);	//这两条线程都变成守护线程
		System.out.println(tg.getName());//输出 "我是一个线程组"
	}

}
class MyRunnable implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for( int i = 0;i < 1000;i++ )
			System.out.println(Thread.currentThread().getName() + "..." + i);
	}
	
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值