一、实现线程三种方式
一、未开启线程并发
public class MyThread {
public static void main(String[] args) {
BranchThread t=new BranchThread();
t.run();
for(int i=0;i<=100;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread extends Thread{
public void run(){
for(int i=0;i<=1000;i++){
System.out.println("分支线程------------->"+i);
}
}
}
1: 自上而下的顺序
2:先输出分支线程1-1000,输出完之后再输出
主线程1-100
一、第一种方式
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。
public class MyThread {
public static void main(String[] args) {
BranchThread t=new BranchThread();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
t.start();
for(int i=0;i<=1000;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread extends Thread{
public void run(){
for(int i=0;i<=1000;i++){
System.out.println("分支线程------------->"+i);
}
}
}
"C:\Program Files\Java\jdk-15.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=64875:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\cao\IdeaProjects\untitled3\out\production\untitled Thread.ThreadTest02
分支线程--------->0
主线程0
分支线程--------->1
主线程1
分支线程--------->2
分支线程--------->3
主线程2
分支线程--------->4
主线程3
分支线程--------->5
主线程4
分支线程--------->6
主线程5
分支线程--------->7
主线程6
分支线程--------->8
主线程7
分支线程--------->9
主线程8
分支线程--------->10
主线程9
分支线程--------->11
主线程10
分支线程--------->12
主线程11
主线程12
分支线程--------->13
主线程13
分支线程--------->14
主线程14
分支线程--------->15
二、第二种方式
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口。
public class MyThread {
public static void main(String[] args) {
/**
* 创建一个可运行的对象
* BranchThread r= new BranchThread();
* 将可运行的对象封装成一个线程对象
* Thread t=new Thread(r)
*/
Thread t = new Thread(new BranchThread());//合并代码
t.start();
for(int i=0;i<=100;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread implements Runnable{
public void run(){
for(int i=0;i<=100;i++){
System.out.println("分支线程------------->"+i);
}
}
}
采用匿名内部类的方式【第二种变形】
public class MyThread {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类方式。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("t线程----->" + i);
}
}
});
t.start();
for (int i = 0; i <= 100; i++) {
System.out.println("main线程----->" + i);
}
}
}
三、第三种方式
实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。
public class MyThread {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
call method begin
/*等待sleep */
call method end!
/*等待另一个线程结束拿到300*/
线程执行结果:300
hello world!
二、线程的生命周期
三、线程的三个基本方法
public class MyThread {
public static void main(String[] args) {
MyThread2 t = new MyThread2();
//获取线程名字
System.out.println(t.getName()); //默认值为 Thread-0
//修改线程名字
t.setName("曹1");
System.out.println(t.getName());//曹1
//获取当前线程对象?
Thread y =Thread.currentThread();
System.out.println(y.getName());//main
t.start();
}
}
class MyThread2 extends Thread{
public void run(){
//获取当前线程对象,t.start();谁启动就是谁
Thread y1= Thread.currentThread();
System.out.println(y1.getName());//曹1
}
}
四、Thread.sleep()
关于Thread.sleep()方法的一个面试题;
1、静态方法:Thread.sleep(1000);
2、参数是毫秒
3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
4、Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
public class MyThread {
public static void main(String[] args) {
//创建一个线程对象,一个多态
Thread t =new MyThread3();
t.setName("t");
t.start();
try {//问题:这行代码会让线程t进入休眠状态吗?
t.sleep(1000*3);//在执行的时候还是会转成:Thread.sleep(1000*5) 这是静态方法
//这行代码的作用是:让当前线程进入休眠,也就是说main进入休眠.
//这样代码出现在main方法中,main线程睡眠。
} catch (InterruptedException e) {
e.printStackTrace();
}
//3秒之后执行到这里
System.out.println("hello word");
}
}
class MyThread3 extends Thread{
public void run(){
for(int i =0;i<10000;i++){
System.out.println(Thread.currentThread().getName()+"------>"+i);
}
}
}
4.1合理终止一个线程
public class MyThread {
public static void main(String[] args) {
MyThread4 r =new MyThread4();
Thread t =new Thread(r);
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}//终止线程
//你想要什么时候终止t的执行,那么你可以把标记修改为false,就结束了。
r.run=false;
}
}
class MyThread4 implements Runnable{
//打一个布尔标记
boolean run=true;
public void run(){
for(int i =0;i<10;i++){
if(run){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//return就结束了,你在结束之前还有什么没保存的。在这里可以保存
//终止当前线程
return;
}
}
}
}
t--->0
t--->1
t--->2
t--->3
t--->4
五 、线程安全问题
1: 什么时候数据在多线程并发的环境下会存在安全问题呢? 三个条件: 条件1:多线程并发。 条件2:有共享数据。 条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
synchronized有三种写法:
第一种:同步代码块
灵活
synchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
synchronized () 括号中中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,
你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个
对象对于t4 t5来说不是共享的。
下面模拟两个人取同一个账户
package ThreadSafe;
public class Account {
private String actno;
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法 第一种方式
public void withdraw(double money) {
synchronized (this) {
//取款之前户余额
double before = this.getBalance();
//取款之后的余额
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
}
/* 第二种方式
public synchronized void withdraw(double money){
double before = this.getBalance(); // 10000
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
} */
package ThreadSafe;
public class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的执行表示取款操作。
// 假设取款5000
double money = 5000;
// 取款
// 多线程并发执行这个方法。
act.withdraw(money);
//第三种方式
/* synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
act.withdraw(money);
}*/
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
}
package ThreadSafe;
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
// 创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 设置name
t1.setName("t1");
t2.setName("t2");
// 启动线程取款
t1.start();
t2.start();
}
}
六、死锁
死锁
package com.bjpowernode.java.deadlock;
/*
死锁代码要会写。
一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2两个线程共享o1,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
七、守护线程
public class ThreadTest14 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("备份数据线程");
//启动线程之前,将线程设置为守护线程
t.setDaemon(true);//守护线程
t.start();
//主线程,主线是用户线程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
public void run(){
int i=0;
//即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动停止。
while (true){
System.out.println(Thread.currentThread().getName()+"--->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.1实现定时器
package com.bjpowernode.java.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
使用定时器指定定时任务。
*/
public class TimerTest {
public static void main(String[] args) throws Exception {
// 创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true); //守护线程的方式
// 指定定时任务
//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-03-14 09:34:30");
//timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
// 每年执行一次。
//timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);
//匿名内部类方式
timer.schedule(new TimerTask(){
@Override
public void run() {
// code....
}
} , firstTime, 1000 * 10);
}
}
// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {
@Override
public void run() {
// 编写你需要执行的任务就行了。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ":成功完成了一次数据备份!");
}
八、wait和notify方法
注意:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
Object o = new Object();
o.wait();
2.wait()方法的作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
3、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
4.notifyAll()方法这个方法是唤醒o对象上处于等待的所有线程。
下面模拟生产者与消费者
public class Box {
//定义一个成员变量,表示第x瓶奶
private int milk;
//定义一个成员变量,表示奶箱的状态
private boolean state=false;
//提供储存和获取牛奶的操作
public synchronized void put( int milk){
//如果有牛奶,等待消费
if(state){
try {
//当前线程进入等待状态,并且释放Producer之前占有的Box锁
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//如果没有牛奶,就生产牛奶
this.milk=milk;
System.out.println("送奶工将第"+this.milk+"瓶奶放入奶箱");
//生产完成之后,修改奶箱状态
state=true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get(){
//如果没有牛奶,等待生产
if(!state){
try {
//当前线程进入等待状态,并且释放Customer之前占有的Box锁。
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//如果有牛奶,就消费牛奶
System.out.println("用户拿到第"+this.milk+"瓶奶");
//消费完毕之后,修改奶箱状态
state=false;
//唤醒其他等待的线程
notifyAll();
}
}
public class Producer implements Runnable{
private Box b;
public Producer(Box b){
this.b=b;
}
@Override
public void run() {
for(int i=1;i<=5;i++){
b.put(i);
}
}
}
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b=b;
}
@Override
public void run() {
while (true){
b.get();
}
}
}
public class BoxDemo {
public static void main(String[] args) {
//创建奶箱对象,这是共享数据区域
Box b =new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用储存牛奶的操作
Producer p =new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c =new Customer(b);
//创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1= new Thread(p);
Thread t2= new Thread(c);
//启动线程
t1.start();
t2.start();
}
}