目录
Java体系的一个重要部分就是多线程。在JAVA程序中,线程的最普遍用途是允许Applet在接受用户输入的同时,在屏幕的另一部分显示动画。使用多线程可以编写出CPU最大利用率的高效程序,因为空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的。
简单计数器
package Thread;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter1 extends Applet {
private int count = 0;
private Button onOff = new Button("Toggle");// 初始化2个按钮
private Button start = new Button("Start");
private TextField t = new TextField(10);// 初始化文本字段对象
private boolean runFlag = true;
private long i = 0; // 循环变量
public void init() {
add(t);
start.addActionListener(new StartL());// 注册按钮
add(start);// 放置按钮
onOff.addActionListener(new OnOffL());
add(onOff);
}
public void go() {
while (true) {
i = 0;
while (i <= 100000000) {
i++;
} // 花费近千毫秒的时间,密集计算
if (runFlag) // 一旦开始,CPU没机会执行其他代码,耗费时间
t.setText(Integer.toString(count++));
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e)// 内部类,实现接口,响应按钮单击事件,其实没执行的机会
{
go();
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) // 内部类,实现接口,响应按钮单击事件,其实没执行的机会
{
runFlag = !runFlag;// 一个标志变量
}
}
public static void main(String[] args)// 应用程序入口,用浏览器时不执行该部分代码
{
Counter1 applet = new Counter1();
Frame aFrame = new Frame("Counter1");
aFrame.addWindowListener(new WindowAdapter() {// 关闭框架按钮事件,没机会执行
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300, 200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
}
一旦按下start按钮,就会调用go()方法,go()方法是永远不会返回的,因为它被设计成一个无限循环。即在第一个按键以后便陷入go()中,go()根本不会返回,程序不能再对其他任何事件进行控制,这时的Toggle按钮和窗口的关闭按钮不再有反应。
Runnable接口
使用接口的原因在于:Applet程序是由于JApplet类扩展而来,因为Java不支持多重继承,所以我们不能同时从JApplet类和Thread类派生类,所以用Runnable接口。
用线程改写上面的代码
package Thread;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter2 extends Applet implements Runnable{//线程接口
private int count = 0;
private Button onOff = new Button("Toggle");// 初始化2个按钮
private Button start = new Button("Start");
private TextField t = new TextField(10);// 初始化文本字段对象
private boolean runFlag = true;
private long i = 0; // 循环变量
Thread athread;//线程对象声明
public void init(){
start.addActionListener(new StartL());
add(start);
start.addActionListener(new OnOffl());
add(onOff);
}
public void run(){
System.out.println("已经调用run()");
while(true){
try{
athread.sleep(3000);//休眠3秒,CPU执行其他代码
}catch(InterruptedException e){
}
if(runFlag){
count++;
System.out.println("count="+count);
}
}
}
class StartL implements ActionListener{
public void actionPerformed(ActionEvent e)//CPU有机会响应按钮的单击事件
{
Counter2 aCounter2=new Counter2();//
athread = new Thread(aCounter2);//线程创立
athread.start();//线程启动
System.out.println("已经按下start按钮");
}
}
class OnOffl implements ActionListener{
public void actionPerformed(ActionEvent e){
System.out.println("已经按下onOff按钮");
}
}
public static void main(String[] args){
Counter2 applet=new Counter2();
Frame aFrame = new Frame("Counter2");
aFrame.addWindowListener(new WindowAdapter() {// 关闭框架按钮事件,没机会执行
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300, 200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
}
程序运行后,单击start按钮开始计数,并显示相应的信息,计数过程中单击Toggle按钮也能做出相应的反应,单击退出可退出。
狂神的多线程代码
package Thread;
//创建线程方式,实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable{
public void run(){
//run方法线程体
for(int i = 0;i<100;i++){
System.out.println("在练花式单杠动作马凯、"+i);
}
}
public static void main(String[] args){
//创建runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(testThread3);
// thread.start();
new Thread(testThread3).start();//上面两行代码可以写成这一句
for(int i=0;i<1000;i++){
System.out.println("我在练单杠360--"+i);
}
}
}
可以从运行结果看粗是同时运行的,不过在把i调整为100以下的就会出现线程堆块。
实现Runnable接口的好处
1.实现接口Runnable具有多线程能力
2.启动线程:传入目标对象+Thread对象.start()
3.避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
狂神的并发问题代码——以抢票为例
package Thread;
//多个线程同时操作一个对象
//买火车票
public class TestThread4 implements Runnable{
//票数
private int ticketNums = 10;//10张票
//重写run方法
public void run(){
while(true){
if(ticketNums<=0){
break;//如果没票了就break跳出
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args){
TestThread4 ticket= new TestThread4();
new Thread(ticket,"俊俊").start();
new Thread(ticket,"俊爷").start();
new Thread(ticket,"虾翻").start();
}
}
这里我们运行可以看到俊爷一个人把所有的票抢光了
然后设置休眠以防止一个人拿走全部的票
在16行代码加入
try{
Thread.sleep(100);//设置休眠200毫秒
}catch(InterruptedException e){
e.printStackTrace();
}
发现拿了超过10张的票,说明多个线程操作同一个资源的情况下,线程不安全,数据出现紊乱。
龟兔赛跑-Race
package Thread;
//模拟龟兔赛跑
public class Race implements Runnable {
//静态的胜利者
private static String winner;
public void run(){
for(int i=0;i<=100;i++){
//判断比赛是否结束
boolean flag = gameOver(i);
if(flag){ //如果flag等于真
break; //如果比赛结束就停止
}
System.out.println(Thread.currentThread().getName()+"跑了————"+i+"步长");
}
}
//判断是否完成比赛
private boolean gameOver(int steep){
//判断是否有胜利者
if(winner != null){
return true;
}{
if(steep>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args){
Race race = new Race();
new Thread(race,"乌龟").start();
new Thread(race,"兔子").start();
}
}
兔子赢了
然后我们让兔子睡觉在第八行加入
//这里让兔子睡觉
if(Thread.currentThread().getName().equals("兔子")&&i%10==0){
try{
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
实现Callable接口(了解即可)
Lamda表达式
--希腊字母表中排序第十一位的字母,英文名称为Lambda
--避免匿名内部类定义过多
--其实质函数式编程的概念
为什么要使用lambda表达式
--去掉一堆没有意义的代码,只留下核心的逻辑
package Thread;
/*
推导lamda表达式
*/
public class TestLamda {
public static void main(String[] args){
Ilike l = new Like();
l.lamda();
}
}
//1.定义一个函数式的接口
interface Ilike{
void lamda();
}
//2.实现类
class Like implements Ilike{
public void lamda(){
System.out.println("牛马努力给老板奋斗法拉利");
}
}
优化。。
package Thread;
/*
推导lamda表达式
*/
public class TestLamda {
//静态内部类
static class Like2 implements Ilike{
public void lamda(){
System.out.println("久旱逢甘露");
}
}
public static void main(String[] args){
Ilike l = new Like();
l.lamda();
l = new Like2();
l.lamda();
//4.局部内部类
class Like3 implements Ilike{
public void lamda(){
System.out.println("他乡遇故知");
}
}
l = new Like3();
l.lamda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类(没有名字,借助了接口)
l= new Ilike(){
public void lamda(){
System.out.println("洞房花烛夜");
}
};
l.lamda();
//6.有lamda简化
l = ()->{
System.out.println("金榜题名时");
};
l.lamda();
}
}
//1.定义一个函数式的接口
interface Ilike{
void lamda();
}
//2.实现类
class Like implements Ilike{
public void lamda(){
System.out.println("牛马努力给老板奋斗法拉利");
}
}
lamda表达式的一些总结
//lamda表达式只能有一行代码的情况下才能简化为一行;如果有多行,那么就用代码块包裹。(即花括号)
//前提是接口为函数式接口
//多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
线程状态
五大状态:创建状态、就绪状态、阻塞状态、运行状态、死亡状态。
线程停止Stop()
package Thread;
//测试stop
//1.建议线程正常停止-->利用次数,不建议死循环
//2.建议使用标志位-->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class StopTest implements Runnable{
//1.设置一个标识位
private boolean flag = true;
public void run(){
int i=0;
while(flag){
System.out.println("runThread"+i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void Stop(){
this.flag = false;
}
public static void main(String[] args){
StopTest ST = new StopTest();
new Thread(ST).start();
for(int i=0; i<100;i++){
System.out.println("main"+i);
if (i == 90) {
//调用stop方法切换标志位,让线程停止
ST.Stop();
System.out.println("stopThread");
}
}
}
}
在main执行到90的时候停止线程
线程休眠Sleep()
>sleep(时间)指定当前线程阻塞的毫秒数
>sleep存在异常InterruptedException
>sleep时间达到后线程进入就绪状态
>sleep可以模拟网络延时,倒计时等
>每一个对象都有一个锁,sleep不会释放锁
package Thread;
//模拟倒计时
public class Sleep {
public static void main(String[] args){
try{
tenDown();
}catch(InterruptedException e){
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException{
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num==0){
break;
}
}
}
}
每隔一秒打印一下
package Thread;
import java.util.Date;
import java.text.SimpleDateFormat;
//模拟倒计时
public class Sleep {
public static void main(String[] args){
//打印当前系统时间
Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间
while(true){
try{
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));//
startTime = new Date(System.currentTimeMillis());//更新当前时间
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void tenDown() throws InterruptedException{
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num==0){
break;
}
}
}
}
第一次运行发现没有类 SimpleDateFormat,于是调用java.text.SimpleDateFormat
观察线程状态
package Thread;
//观察测试线程的状态
public class TestState{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后
thread.start();//启动线程
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED) {//只要线程不终止,就一直输出状态
Thread.sleep(100);//这里抛出一个异常
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
}
}
这里我们试一下线程停止后还能否正常启动,在while循环后添加thread.start()
运行发现没办法再次启动线程。
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
package Thread;
//测试线程的优先级
public class Testpriority {
public static void main(String[] args){
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread());
Mypriority myPriority = new Mypriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
//先不设置优先级,启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIPRITY
t4.start();
}
}
class Mypriority implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
可以看到优先级高的先跑,但是你得设置优先级(优先级数为1~10)
要注意的是这里可能会出现性能倒置:优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调度了,这都是看CPU的调度。
守护(daemon)线程
(1)线程分为用户线程和守护线程
(2)虚拟机必须确保用户线程执行完毕
(3)虚拟机不用等待守护线程执行完毕
package Thread;
//测试守护线程
//上帝守护你,人生不过三万天
public class TestDaemon {
public static void main(String[] args){
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认false表示用户线程
thread.start();//上帝线程启动
new Thread(you).start();//你 用户线程启动
}
}
//上帝
class God implements Runnable{
public void run(){
while (true) {
System.out.println("你被上帝保护着");
}
}
}
//你
class You implements Runnable {
public void run() {
for (int i = 1; i < 36500; i++) {
System.out.println("你活到了"+i+"天");
}
System.out.println("差不多活够了,该狗带了");
}
}
虚拟机关闭需要一定的时间,所以用户线程结束后,守护线程还在运行。
线程同步
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放即可。
三大不安全案例
1.不安全的买票
package Thread;
//不安全的买票
//线程不安全,会有负数
public class UnsafeBuyTicket {
public static void main(String[] args){
BuyTicket station = new BuyTicket();
new Thread(station,"niuam").start();
new Thread(station,"niuam1").start();
new Thread(station,"niuam2").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
public void run(){
//买票
while (flag) {
try{
buy();//调用方法buy买票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException{
//判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
2.银行取钱
package Thread;
//不安全的取钱
//两个人同时取钱
public class UnsafeBank {
public static void main(String[] args){
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"牛马");
Drawing girlfriend = new Drawing(account,100,"牛马de女票");
you.start();
girlfriend.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
//Alt+insert构造函数
//按住Ctrl选中两个
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
//取了多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;
public Drawing (Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
public void run(){
//判断有没有钱
if(account.money-drawingMoney<0){
//钱不够,要取的钱比账户余额多
System.out.println(Thread.currentThread().getName()+"钱不够,不能操作");
return;
}
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取走的钱
account.money = account.money - drawingMoney;
//你现在手里的
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
//Thread.currountThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
这里相当于负债了,两人操作一个账户,但是在不同的内存地址中。
3.线程不安全的情况
两个线程在一瞬间同时加载到了同一个位置中,把数据加载在同一位置,然后覆盖掉了。就会缺失掉一部分线程。
死锁
死锁避免方法:
产生死锁的四个条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
(4)循环等待条件:若进程之间形成一种头尾相接的循环等待资源关系。
Lock(锁)
package Thread;
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
public class TestLock {
public static void main(String[] args){
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
//定义票数
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock =new ReentrantLock();
public void run(){
while(true){
// lock.lock();
try {
lock.lock();
if(ticketNums>0){
if(ticketNums>0){
try{
lock.lock();//加锁
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}
} finally {
//解锁
lock.unlock();
}
}
}
}
排队一个一个来拿票。
写到这突然想更新一下自己的IDEA2020.3.1,预期可能会现问题,然后果然不出意料,更新出错,IDEA也没法用了,本来是想试试能不能通过更新解决之前没有解决的代码自动补全功能。结果芭比Q了。于是只好卸载然后重新安装一个了,这里有IDEA 2019的资源。IntelliJ IDEA 2019.3中/英文破解版64位下载|兼容WIN10 | 我爱分享网 (zhanshaoyi.com)
我没有进行汉化,原先用的汉化版的,感觉不好用,这里解决了代码自动补全的功能,方便多了。
新的IDEA2019!哈哈。。。
以后可别想着通过更新解决问题了,哈哈(当然不全对~_~)
新的IDEA很多要重新设置,比如鼠标控制调节字体大小,
勾选后Apply后点OK就行。
管程法
package newbegin;
//测试:生产者消费者模型-->利用缓冲区解决;
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Cousumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
//生产工作
public void run(){
for (int i = 0; i <100 ; i++) {
container.push(new Chicken(i));//把生产的鸡丢经容器
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Cousumer extends Thread{
SynContainer container;
public Cousumer(SynContainer container){
this.container = container;
}
//消费
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;//产品鸡的编号
public Chicken(int id){
this.id=id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[10];//一次性10只鸡
int count=0;//这里是容器计数器
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者消费
if(count==chickens.length){
//通知消费者消费,生产者等待
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//这里容器没满,我们把产品丢入
chickens[count]=chicken;
count++;
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if(count==0){
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//这里可以消费
count--;
Chicken chicken = chickens[count];
//吃完通知生成者生产
this.notifyAll();
return chicken;
}
}
生产的产品都能被消费者消费到。
线程池
使用背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似于公共交通工具,共享单车。
优点:
(1)提高响应速度(减少创建新线程的时间)
(2)降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
(3)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize;最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
上代码:
package newbegin;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭线程池连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
总结
package newbegin;
import org.w3c.dom.ls.LSOutput;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//总结线程的创建
public class ThreadNew {
public static void main(String[] args) {
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
try{
Integer integer = (Integer) futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
}
}
//1.继承Thread类
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("启动线程1");
}
}
//2.实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("启动线程2");
}
}
//3.实现Callable接口
class MyThread3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("启动线程3");
return 100;
}
}
三个线程正常启动。
花费了将近3个星期学习,零零散散的知识点慢慢得靠项目组织。继续梭哈JAVA这条艰难的道路吧。
(我爱技术,技术爱我)