创建两个分线程,干不同的事情
package com.codeday22.demo01;/*** 创建两个线程,一个遍历100内偶数,另外一个遍历100内奇数**/public class ThreadDemo {public static void main(String[] args) {// MyThread1 t1 = new MyThread1();// t1.start();//// MyThread2 t2 = new MyThread2();// t2.start();// 也可以创建Thread的匿名子类new Thread(() -> {for (int i = 0; i < 100; i++) {if (i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}).start();new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 1){System.out.println(Thread.currentThread().getName() + ":" + i);}}}}.start();}}//class MyThread1 extends Thread{// @Override// public void run() {// for (int i = 0; i < 100; i++) {// if (i % 2 == 0){// System.out.println(MyThread1.currentThread().getName() + ":" + i);// }// }// }// }////class MyThread2 extends Thread{// @Override// public void run() {// for (int i = 0; i < 100; i++) {// if (i % 2 == 1){// System.out.println(MyThread1.currentThread().getName() + ":" + i);// }// }// }//}
包含了匿名内部类和写两个子类的方法
Thread类的常用方法:
- 测试Thread中的常用方法:
- 1、
start():启动当前线程,调用当前线程的run(); - 2、
run():通常需要重写此方法,将要实现的代码写在这个方法中; - 3、
currentThread():静态方法,返回当前代码的线程; - 4、
getName():获取当前线程的名字; - 5、
setName():设置当前线程的名字; - 6、
yeld():释放CPU,然后所有线程去抢; - 7、
join():让线程a阻塞,线程b加入进来,线程b执行完毕线程a才结束阻塞状态; - 8、
stop():结束线程的运行,已过时; - 9、
sleep():让线程休眠,单位毫秒; - 10、
isAlive():判断当前线程是否存活。
线程的调度:
2、如何测试线程的优先级:
getPriority():获取当前线程的优先级;setPriority():设置分线程的优先级,CPU只是参考优先级设置,不是把优先级高的跑完再跑优先级低的。
卖票:
package com.codeday23.demo01;/*** 创建三个窗口买票,总票数100张*/public class Window extends Thread{private static int ticket = 100;// 这里有线程的安全问题@Overridepublic void run() {while(true){if(ticket > 0) {System.out.println(getName() + ":卖票,票号为:" + ticket);ticket--;}else{break;}}}}package com.codeday23.demo01;public class WindowTest {public static void main(String[] args) {Window t1 = new Window();Window t2 = new Window();Window t3 = new Window();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}
多线程的方式二:
package com.codeday23.demo01;/*** 创建多线程的方式二:实现Runnable接口* 1、创建一个实现了Runnable接口的类* 2、实现类去实现Runnable中的抽象方法:run()* 3、创建实现类的对象* 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象* 5、通过Thread类的对象调用start()**///创建一个实现了Runnable接口的类class MThread implements Runnable{//实现类去实现Runbale中的抽象方法:run()@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(i);}}}}public class ThreadTest {public static void main(String[] args) {//创建实现类的对象MThread mThread = new MThread();// 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread t1 = new Thread(mThread);// 通过Thread类调用start():①启动线程;②调用当前线程的run()-->调用了// Runnable类型的targett1.start();// 再启动一个线程,遍历100以内的偶数Thread t2 = new Thread(mThread);t2.start();}}
开发中有限选择实现runnable接口的方式。
原因:
1、实现的方式没有类的单继承性的局限性;
2、实现的方式更适合来处理多个线程有共享数据的情况。
联系:
1、两种方式都需要重写run(),将线程要执行的逻辑声明再run()中。
线程的生命周期:

多线程的同步:
卖票过程中出现重票、错票,原因是一个线程没有执行完毕,其他线程就参与了进来。
如何解决:用🔒
方式一:同步代码块
synchronized(同步监视器){// 需要被同步的代码}
说明:
1、操作共享数据的代码,即为需要被同步的代码; 不能包含代码多了,也不能包含少了。
2、共享数据:多个线程共同操作的变量。比如:ticket;
3、同步监视器,就是🔒。任何一个类的对象,都可以充当锁,多个线程必须公用同一把🔒。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器。
package com.codeday23.demo02;class Window1 implements Runnable{private int ticket = 100;Object obj = new Object();@Overridepublic void run() {while(true){ // 这里的while语句是为了让程序执行完一次后退出循环重新判断条件// 不加while,程序会一直在里面执行synchronized (obj) {if (ticket > 0) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);ticket--;} else {break;}}}}}public class WindowTest1 {public static void main(String[] args) {Window1 w1 = new Window1();Thread t1 = new Thread(w1);Thread t2 = new Thread(w1);Thread t3 = new Thread(w1);t1.start();t2.start();t3.start();}}
方式二:同步方法
如果操作共享数据的代码完整地生命在一个方法中,可以将此方法声明为同步。
总结:
1、同步方法依然涉及同步监视器,只是不需要我们显式的声明;
2、非静态的同步方法,同步监视器是this,静态的同步方法是this.class。
package com.codeday23.demo02;public class WindowTest3 {public static void main(String[] args) {Window1 w1 = new Window1();Thread t1 = new Thread(w1);Thread t2 = new Thread(w1);Thread t3 = new Thread(w1);t1.start();t2.start();t3.start();}}class Window3 implements Runnable{private int ticket = 100;@Overridepublic void run() {while(true){show();}}private synchronized void show(){// 同步监视器是:thisif (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);ticket--;}}}
继承Thread类的多线程方法使用同步方法:
package com.codeday23.demo02;import com.codeday23.demo01.Window;public class WindowTest4 {public static void main(String[] args) {Window4 t1 = new Window4();Window4 t2 = new Window4();Window4 t3 = new Window4();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}class Window4 extends Thread{private static int ticket = 100;// 这里有线程的安全问题@Overridepublic void run() {while(true){show1();}}private static synchronized void show1(){// 此时的同步监视器是this.classif(ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;}}}
单例设计模式:
1、所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例。
①饿汉式:私有化构造器,私有的静态属性是new一个对象,使用静态方法去调用这个属性,由于是静态的,所有怎么都只有一个对象;
②懒汉式:私有化构造器,私有的静态属性是一个空对象,判断instance是否为null,如果是null,new一个对象,否则直接返回instance。饿汉式是一个套娃结构,如果第一次调用get方法,会new一个该对象,第二次再调用,这个对象已经new了一个实例,就会跳过。
懒汉式:
class Bank1{private Bank1(){}private static Bank1 b1 = null;// 这里需要注意,这只是调用构造器之后的初始化,// 并不是说b1一直都是nullpublic static Bank1 getInstance(){if(b1 == null){Bank1 b1 = new Bank1();}return b1;}}public class BankTest {public static void main(String[] args) {Bank b1 = Bank.getInstance();Bank b2 = Bank.getInstance();System.out.println(b1 == b2);}}

使用同步机制将单例模式中的懒汉式改写为线程安全的:
class Bank {private Bank() {}private static Bank instance = null;public static 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.codeday23.demo03;public class ThreadTest {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic 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(){@Overridepublic 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();}}
我的理解:
首先,统共两个线程,不分先后顺序。当线程1执行时,手中拿着S1的锁,代码块被执行,然后阻塞100ms。与此同时,在线程1阻塞的过程中,线程2极有可能开始执行,并且手中持有S2的锁。当线程1醒后,想去拿S2的锁执行下一个代码块的内容,发现线程2还在阻塞,无法拿到,就开始僵持,出现死锁。
线程安全方法三,公平锁:
package com.codeday23.demo03;import java.util.concurrent.locks.ReentrantLock;/*** 解决线程安全问题的方式三:Lock锁 -----JDK 5.0新增**/class Window implements Runnable{private int ticket = 100;private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {//2、调用锁定的方法locklock.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、调用解锁的方法lock.unlock();}}}}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("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}
同步和公平锁的区别:
相同:二者都是用来解决线程安全问题的。
不同:同步在执行完相应的代码后自动解锁,lock需要手动实现解锁
注意:lock对象同样需要做到统一,不能有多个lock对象。
一个三个用户存款的实现代码:
package com.codeday24.demo01;import com.codeday13.A;import java.util.AbstractCollection;/*** 银行有一个账户** 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。** 分析:* 1、是否有多线程问题? 有* 2、是否有共享数据? 是* 3、是否有线程安全问题? 是* 4、需要考虑如何解决线程安全问题。同步机制,三种方式。**/class Account{private static double balance;public static void deposit(double amt){balance += amt;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(balance >= 0) {System.out.println(Thread.currentThread().getName() + "存钱成功,余额为:" + balance);}else{System.out.println("余额不足!!");}}public double getBalance(){return balance;}}class Customer extends Thread{private Account acct;public Customer(Account acct) {this.acct = acct;}private static Object obj = new Object();@Overridepublic void run() {synchronized (obj){for (int i = 0; i < 3; i++) {acct.deposit(1000);}}}}public class AccountTest {public static void main(String[] args) {Account acct = new Account();Customer c1 = new Customer(acct);Customer c2 = new Customer(acct);c1.setName("甲");c2.setName("乙");c1.start();c2.start();}}
新增线程创建方式,实现Callable接口:
package com.codeday25.demo01;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.Future;import java.util.concurrent.FutureTask;/*** 创建线程得方式三:实现Callable接口。** 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建都线程方式强大?* 1、call()有返回值;* 2、call()方法可以抛出异常,被外面的操作捕获;* 3、Callable支持泛型**/// 1、创建一个实现Callable的实现类class NumThread implements Callable{// 实现call方法,将此线程需要执行的操作写在这里面。@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; 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 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.codeday25.demo02;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;/*** 创建线程的方式四:创建线程池** 优点:* 提高响应速度(减少了创建新线程的时间)* 降低资源消耗(重复利用线程池中线程,不需要每次都创建)* 便于线程管理** 1)corePoolSize:核心池的大小* 2)maximumPoolSize:最大线程数* 3)keepAliveTime:线程没有任务时最多保持多长时间后会终止** 创建多线程有四种方式*/class NumberThread implements Runnable {@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}}class NumberThread1 implements Runnable{@Overridepublic 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());// 获取该对象的类,结果是ThreadPoolExecutorservice1.setCorePoolSize(15);// service1.setKeepAliveTime();// 2、执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());// 适用于Runnableservice.execute(new NumberThread1());// 适用于Runnable// service.submit();// 适用于Callable// 3、关闭连接池service.shutdown();// 关闭线程池}}
