自学JavaDay14
多线程
多线程的创建
package com.th1024.multiplethreads.ThreadDemo;
/**
* 多线程的创建
*
* 方式一:继承于Tread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类中的run()方法
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* 例子:遍历100以内的所有偶数
*
* 方式二:实现Runnable接口--开发中优先选择
* 1. 定义子类,实现Runnable接口
* 2. 子类中重写Runnable接口中的run方法
* 3. 通过Thread类含参构造器创建线程对象
* 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
* 5. 调用Thread类的start方法:开启线程,调用Runnable子类接口中的run方法
* 优点:
* 1. 避免了单继承的局限性
* 2. 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
*
* @author TuHong
* @create 2021-01-23 14:05
*/
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread1 t1 = new MyThread1();
//4. 通过此对象调用start()--分线程执行run()方法
t1.start();
// t1.run();//只是在主线程调用run方法
//启动其他线程,不能通过已经start()的线程去执行
// t1.start();//异常---IllegalThreadStateException
//需重新创建一个线程的对象
// MyThread1 t2 = new MyThread1();
// t2.start();
//主线程
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
//3. 通过Thread类含参构造器创建线程对象
//4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
MyThread2 myThread2 = new MyThread2();
//5. 调用Thread类的start方法:开启线程,调用Runnable子类接口中的run方法
Thread t3 = new Thread(myThread2);
t3.start();
//再启动一个线程,输出100以内的质数
Thread t4 = new Thread(myThread2);
t4.start();
//创建匿名子类的方式创建线程
// new Thread(new MyThread2()).start();
}
}
//1. 创建一个继承于Thread类的子类
class MyThread1 extends Thread{
//2. 重写Thread类中的run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//1. 定义子类,实现Runnable接口
class MyThread2 implements Runnable{
//2. 子类中重写Runnable接口中的run方法
@Override
public void run() {
//输出100以内的质数
label:for (int i = 2; i < 100; i++) {
for (int j = 2; j <= Math.sqrt(i); j++) {
if(i % j == 0){
continue label;
}
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Thread中常用的方法
package com.th1024.multiplethreads.ThreadDemo;
/**
* 测试Thread中的常用方法:
* 1. start():启动当前线程,调用当前线程的run()
* 2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权
* 7. join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完之后,
* 线程a才会结束阻塞状态
* 8. sleep(long millis):让当前线程“睡眠”指定的毫秒。在这段时间内,线程是阻塞状态
* 9. isAlive():判断当前线程是否存活
*
* @author TuHong
* @create 2021-01-24 10:50
*/
public class ThreadMethodTest {
public static void main(String[] args) {
Thread1 t1 = new Thread1("Thread1");
// t1.setName("线程1");
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){
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断分线程是否存活
System.out.println(t1.isAlive());
}
}
class Thread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
// if(i % 20 == 0){
// yield();
// }
}
}
public Thread1(String name){
super(name);
}
}
两种方式解决线程不安全问题
package com.th1024.demo;
/**
* 例子:创建三个窗口卖票,总票数为100张
*
* 方式一:使用继承Thread类的方式
* 存在线程安全问题
*
* 方式二:使用实现Runnable接口的方式
* 存在线程安全问题
*
* 1. 问题:重票和错票
* 2. 原因:某个线程进行操作共享数据时,还没执行完,其它线程就参与进来也进行了操作
* 3. 解决:当一个线程操作共享数据时,其它线程不能参与进来,直到线程操作完成时,其它线程才可以操作,
* 即使该线程出现阻塞,其它线程也不能参与
* 4. 方式:同步机制
* 优点:解决了线程不安全问题
* 缺点:操作同步代码时,只能有一个线程参与,相当于单线程,效率低
*
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:1. 操作共享数据的代码,即为需要被同步的代码
* 2. 共享数据:多个线程共同操作的变量
* 3. 同步监视器。俗称:锁。任何一个类的对象都可以充当锁
* 要求:多个线程必须拥有同一个监视器对象
*
* 方式二:同步方法
* 如果操作共享数据的代码完整地声明在一个方法中,不妨将这个方法声明为同步方法
* 说明:1. 同步方法仍然涉及到同步监视器,只是不需要显式声明
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
*
* @author TuHong
* @create 2021-01-24 13:18
*/
class Window1 extends Thread{
private static int ticket = 100;
static Object obj = new Object();//声明为静态,每个对象共享一个静态变量
@Override
public void run() {
while(ticket > 0){
//while(true) {
//同步代码块解决继承Thread类方式的线程安全问题
// synchronized(Window1.class){//使用Window1类作为对象,只会加载一次 //synchronized (obj) {//不能使用this,Window1的对象不唯一
// if (ticket > 0) {
//
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println(getName() + ": 卖票,票号为:" + ticket);
// ticket--;
// } else {
// break;
// }
sell();
// }
}
}
private static synchronized void sell(){//同步监视器:Window1.class
//private synchronized void sell(){//同步监视器:w1,w2,w3
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}
}
}
class Window2 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
while(ticket > 0){
//while(true) {
//同步代码块解决实现Runnable接口方式的线程安全问题
// synchronized(this){//this代表唯一的Window2的对象 //synchronized (dog) {
// if (ticket > 0) {
//
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
// ticket--;
// } else {
// break;
// }
sell();
// }
}
}
private synchronized void sell(){//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest {
public static void main(String[] args) {
// Window1 w1 = new Window1();
// Window1 w2 = new Window1();
// Window1 w3 = new Window1();
//
// w1.setName("窗口一");
// w2.setName("窗口二");
// w3.setName("窗口三");
//
// w1.start();
// w2.start();
// w3.start();
Window2 window2 = new Window2();
Thread t1 = new Thread(window2);
Thread t2 = new Thread(window2);
Thread t3 = new Thread(window2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
//class Dog{
//
//}
例子:解决单例模式中的懒汉式的线程不安全问题
package com.th1024.demo;
/**
* 使用同步机制将单例模式中的懒汉式改写为线程安全的
*
* @author TuHong
* @create 2021-01-25 11:20
*/
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance() {
// public static synchronized Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
死锁
package com.th1024.demo;
/**
* 演示线程的死锁问题
*
* 1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
* 2. 说明:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
*
*
* @author TuHong
* @create 2021-01-25 11:37
*/
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决线程不安全问题方式三:Lock锁
package com.th1024.demo;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式三:Lock锁 -- JDK5.0新增
*
* 1. 面试题:synchronized和lock的异同?
* 相同:都可以解决线程不安全的问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动地释放同步监视器
* lock需要手动地启动同步(lock()),同时结束同步也需要手动地实现(unlock())
* 优先使用顺序:lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
*
* 2. 面试题:如何解决线程安全问题?有几种方式?
*
* @author TuHong
* @create 2021-01-25 13:23
*/
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
//2. 调用锁定方法:lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售出,票号为 - " + ticket);
ticket--;
} else {
break;
}
}finally {
//3. 调用解锁方法:unlock()
lock.unlock();
}
}
}
}
创建多线程的另外两种方式
实现Callable接口
package com.th1024.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。-- JDK5.0新增
*
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口的方式强大?
* 1. call()方法可以有返回值
* 2. call()可以抛出异常
* 3. Callable支持泛型
*
* @author TuHong
* @create 2021-01-25 16:06
*/
//1. 创建一个实现Callable接口的实现类
class NumThread implements Callable{
//2. 实现call(),将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3. 创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4. 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中
FutureTask futureTask = new FutureTask(numThread);
//5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并start()
new Thread(futureTask).start();
try {
//6. 获取Callable中call()的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
使用线程池
package com.th1024.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 提高响应速度(减少了创建新线程的时间)
* 降低资源消耗(重复利用线程池中,不需要每次都创建)
* 便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
* @author TuHong
* @create 2021-01-25 16:30
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime(10);
//2. 执行指定的线程操作,需提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适用于Runnable
service.execute(new NumberThread1());//适用于Runnable
// service.submit();//适用于Callable
//3. 关闭连接池
service.shutdown();
}
}