创建两个分线程,干不同的事情
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(){
@Override
public 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;// 这里有线程的安全问题
@Override
public 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()
@Override
public 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类型的target
t1.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();
@Override
public 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;
@Override
public void run() {
while(true){
show();
}
}
private synchronized void show(){// 同步监视器是:this
if (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;// 这里有线程的安全问题
@Override
public void run() {
while(true){
show1();
}
}
private static synchronized void show1(){// 此时的同步监视器是this.class
if(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一直都是null
public 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(){
@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();
}
}
我的理解:
首先,统共两个线程,不分先后顺序。当线程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();
@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、调用解锁的方法
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();
@Override
public 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方法,将此线程需要执行的操作写在这里面。
@Override
public 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 {
@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());// 获取该对象的类,结果是ThreadPoolExecutor
service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
// 2、执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());// 适用于Runnable
service.execute(new NumberThread1());// 适用于Runnable
// service.submit();// 适用于Callable
// 3、关闭连接池
service.shutdown();// 关闭线程池
}
}