【java学习笔记】线程
1.线程的定义
①继承Thread类,将执行的任务逻辑放到run方法中,调用start方法来开启线程
public class ThreadDemo {
public static void main(String[] args) {
TDemo t = new TDemo();
// 开启线程
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
}
class TDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 9; i++) {
System.out.println("Thread:" + i);
}
}
}
②实现Runnable,重写run方法,需要利用Runnable对象来构建一个Thread对象从而启动线程
由于java是单继承的,因此当一个类已经继承了父类时,便不能继承Thread类。而又希望启用线程,此时实现Runnable接口即可达到目的。
public class RunnableDemo {
public static void main(String[] args) {
RDemo r = new RDemo();
// 通过Runnable对象来构建一个Thread对象
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
}
class RDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread:" + i);
}
}
}
③实现Callable<T>,重写call方法
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newCachedThreadPool();
Future<String> f = es.submit(new CDemo());
System.out.println(f.get());
}
} class CDemo implements Callable<String> {
@Override
public String call() throws Exception {
return "hahah~~~";
}
}
2.线程的状态

创建:新创建了一个线程对象。
就绪:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。
3.常见的线程方法
① Thread(String name) 初始化线程的名字
② getName() 返回线程的名字
③ setName(String name) 设置线程对象名
④ sleep() 线程睡眠指定的毫秒数。
⑤ getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
⑥ setPriority(int newPriority) 设置线程的优先级。(最大的优先级是10,最小的1,默认是5)虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现。
⑦ currentThread() 返回CPU正在执行的线程的对象。
4.多线程的并发安全问题
线程的执行不存在先后,相互抢占执行,抢占并不是只发生在线程执行的开始,而是发生在线程执行的每一步过程中。由于多个线程并发导致出现了一些不符合常理的数据的现象,即线程安全问题。
4.1出现线程安全的根本原因
①存在两个或者两个以上的线程对象共享同一个资源
②多线程操作共享资源的代码有多句
4.2线程安全问题的解决方案
1.可以使用同步代码块去解决。
synchronized(锁对象){
需要被同步的代码
}
注意事项:
①锁对象可以是任意一个对象
②一个线程在同步块中sleep,并不会释放锁对象
③如果不存在线程安全问题,千万不要使用同步代码块,因为会降低效率
④锁对象必须是多线程共享的一个资源,否则锁不住
2.同步函数 就是使用synchronized修饰的方法
注意事项:
①如果是一个非静态的同步函数,锁对象是this;如果是静态的同步函数,锁对象是当前函数所属的类的字节码文件(class)
②同步函数的锁对象是固定的,不能由开发者来指定
推荐使用同步代码块
①同步代码块的锁对象可以由开发者指定,方便控制。而同步方法是固定的。
②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步
例:模拟取款,
public class Bank {
public static void main(String[] args) {
//创建一个账户
Account account = new Account("10086", 1000);
//模拟两个线程对同一个账户取钱
new DrawThread("张三",account,800).start();
new DrawThread("李四", account, 900).start();
}
}
class DrawThread extends Thread{
//模拟用户账户
private Account account;
//当前取钱线程所希望取得钱数
private double drawMoney;
public DrawThread(String name, Account account, double drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
/*
* 虽然java程序允许任何对象作为同步监视器,但是同步监视器的目的:
* 阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源
* 当做同步监视器
*
* 加锁-修改-释放锁
*
* 字节码文件
* 静态变量
* "锁对象"
*
*/
synchronized (account) {
//synchronized (this) { //非共享,失败
//synchronized (new String("")) { //非共享,失败
//synchronized ("") {
//账户余额大于取钱数目
if(account.getBalance() >= drawMoney) {
//吐出钞票
System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney);
// try {
// Thread.sleep(5000);
// } catch (Exception e) {
// e.printStackTrace();
// }
//修改余额
account.setBalance(account.getBalance() - drawMoney);
System.out.println("\t余额为:" + account.getBalance());
} else {
System.out.println(getName() + "取钱失败!余额不足!");
}
}
}
}
class Account{
//封装账户编号、账户余额两个成员变量
private String accountNo;
private double balance;
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
synchronized代码块Demo
public class Bank2 {
public static void main(String[] args) {
//创建一个账户
Account2 account = new Account2("10086", 1000);
//模拟两个线程对同一个账户取钱
new DrawThread2("张三",account,800).start();
new DrawThread2("李四", account, 900).start();
}
}
class DrawThread2 extends Thread{
//模拟用户账户
private Account2 account;
//当前取钱线程所希望取得钱数
private double drawMoney;
public DrawThread2(String name, Account2 account, double drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
account.draw(drawMoney);
}
}
class Account2{
//封装账户编号、账户余额两个成员变量
private String accountNo;
private double balance;
public Account2(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
/*
* 锁对象是this
* 对于同一个Account账户而言,任意时刻只能有一个线程获得对account对象的锁定
*/
public synchronized void draw(double drawMoney) {
//账户余额大于取钱数目
if(balance >= drawMoney) {
//吐出钞票
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawMoney);
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
//修改余额
balance -= drawMoney;
System.out.println("\t余额为:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
}
}
}
synchronized方法Demo
成功的情况:

失败的情况:

5.死锁
由于多个线程之间的锁形成了嵌套导致程序无法继续运行的现象。
避免死锁:减少线程数量,统一锁对象,减少锁嵌套。
public class DeadLockDemo {
public static void main(String[] args) {
new Thread(new Runnable() { // 创建线程
public void run() {
synchronized ("资源1") {
System.out.println(Thread.currentThread().getName() + ":得不到资源2,就不释放资源1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("资源2") {
System.out.println(Thread.currentThread().getName() + ":得到资源2,释放资源1");
}
}
}
}, "线程A").start();
new Thread(new Runnable() { // 美国人
public void run() {
synchronized ("资源2") { // 美国人拿到了筷子
System.out.println(Thread.currentThread().getName() + ":得不到资源1,就不释放资源2");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("资源1") {
System.out.println(Thread.currentThread().getName() + ":得到资源1,释放资源2");
}
}
}
}, "线程B").start();
}
}
DeadLockDemo
死锁:

正常:

6.等待唤醒机制
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。
①notify
通过等待唤醒机制调节了线程之间的执行顺序
public class WaitNotifyDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("Tom");
s.setGender('男');
new Thread(new Ask(s)).start();
new Thread(new Change(s)).start();
}
}
class Change implements Runnable {
private Student s;
public Change(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (s.getGender() == '男') {
s.setName("Amy");
s.setGender('女');
} else {
s.setName("Tom");
s.setGender('男');
}
s.flag = true;
// 唤醒在等待的线程
s.notify();
}
}
}
}
class Ask implements Runnable {
private Student s;
public Ask(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是" + s.getName() + ",我是" + s.getGender());
s.flag = false;
s.notify();
}
}
}
}
class Student {
private String name;
private char gender;
// 标记位---规定flag为true,执行ask线程,如果flag为false执行change
public boolean flag = true;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
WaitNotifyDemo
②notifyAll
线程在等待期间是在这个锁所对应的线程池中等待的。线程池本质上是一个存储线程的队列。
若是用notify,唤醒队列中的第一个线程,执行方式如下:
第一个括号代表就绪的线程,第二个括号代表线程池中的线程。
(a1,a2,c1,c2)() -> a1 running-> (a1,a2,c1,c2)() -> a1 running -> (a2,c1,c2)(a1) -> a2 running -> (c1,c2)(a1,a2) -> c1 running -> (a1,c1,c2)(a2) -> c1 running -> (a1,c2)(a2,c1) -> c2 running -> (a1)(a2,c1,c2) -> a1 runnig -> (a1,a2)(c1,c2) -> a1 running -> (a2)(c1,c2,a1) -> a2 running -> ()(c1,c2,a1,a2)
最后,所有线程都在线程池中阻塞,发送死锁。此时需要用到notifyAll。
public class WaitNotifyAllDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("Tom");
s.setGender('男');
new Thread(new Ask2(s)).start();
new Thread(new Ask2(s)).start();
new Thread(new Change2(s)).start();
new Thread(new Change2(s)).start();
}
}
class Change2 implements Runnable {
private Student s;
public Change2(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
while (s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (s.getGender() == '男') {
s.setName("Amy");
s.setGender('女');
} else {
s.setName("Tom");
s.setGender('男');
}
s.flag = true;
// 唤醒在等待的线程
s.notifyAll();
}
}
}
}
class Ask2 implements Runnable {
private Student s;
public Ask2(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
while (!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是" + s.getName() + ",我是" + s.getGender());
s.flag = false;
s.notifyAll();
}
}
}
}
WaitNotifyAllDemo
sleep与wait的区别:
①sleep在使用的时候需要指定休眠时间,到点自然醒。释放执行权,不释放锁。是一个静态方法,设计在了Thread类上
②wait在使用的时候可以指定等待时间,也可以不指定,如果不指定等待时间就需要唤醒。释放执行权,释放锁。是一个非静态方法,设计在了Object类上
例:生产消费模型
一个线程表示生产者Producer,一个线程表示消费者Consumer,商品的总数量不超过1000。
public class ProductAndConsumer {
public static void main(String[] args) {
Product p = new Product();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}
class Producer implements Runnable {
private Product p;
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 计算本次商品所能生产的最大数量
int max = 1000 - p.getCount();
// 计算本次生产的商品数量
int count = (int) (Math.random() * (max+1));
// 本次提供的商品的总数量
p.setCount(p.getCount() + count);
System.out.println("本次生产数量:" + count + ", 本次提供商品数量为:" + p.getCount());
p.flag = true;
p.notify();
}
}
}
}
class Consumer implements Runnable {
private Product p;
public Consumer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (!p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 计算本次消费的数量
int count = (int) (Math.random() * (p.getCount() + 1));
// 计算本次的剩余数量
p.setCount(p.getCount() - count);
System.out.println("本次消费数量:" + count + ", 本次剩余商品数量:" + p.getCount());
p.flag = false;
p.notify();
}
}
}
}
class Product {
private int count;
public boolean flag = false;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
ProductAndConsumer
7.守护线程
守护别的线程。当被守护的线程结束,守护线程无论执行完成与否都得随之结束。
一个线程要么是守护线程要么是被守护的线程。守护线程是随着最后一个被守护线程的结束而结束,例如GC。
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Soilder(), "小兵1号");
Thread t2 = new Thread(new Soilder(), "小兵2号");
Thread t3 = new Thread(new Soilder(), "小兵3号");
Thread t4 = new Thread(new Soilder(), "小兵4号");
// 设置为守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
t1.start();
t2.start();
t3.start();
t4.start();
for (int i = 10; i > 0; i--) {
System.out.println("Boss掉了一滴血,剩余" + i);
Thread.sleep(150);
}
}
}
class Soilder implements Runnable {
@Override
public void run() {
for (int i = 100; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + "掉了一滴血,剩余" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
DaemonDemo
8.线程的优先级
线程的优先级分为1-10,理论上数字越大等级越高,这个线程抢到资源的几率就越大。相邻的两个线程的优先级的差异性不明显。至少要相差5个等级才能体现的相对明显一点点。
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new PDemo(), "A");
Thread t2 = new Thread(new PDemo(), "B");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
// 获取线程的优先级
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
}
}
class PDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
PriorityDemo
【java学习笔记】线程的更多相关文章
- Java学习笔记 线程池使用及详解
有点笨,参考了好几篇大佬们写的文章才整理出来的笔记.... 字面意思上解释,线程池就是装有线程的池,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程 ...
- Java 学习笔记 线程控制
题目一 本质上来说,线程是不可控制的,线程的执行是由CPU资源分配决定的,我们无法干预系统CPU的资源分配,但我们可以增加条件来让线程按照我们的预想顺序来执行. 比如.如果当前的执行的线程不满足我们所 ...
- java学习笔记 - 线程池(一)
线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销 优点:(面试题)可重复使用已有线程,避免对象创建.消亡和过度切换的性能开 ...
- java学习笔记 线程的实现与同步
2019.4.2 线程实现的两种方式 继承线程,复写其中的run方法 实现runnable接口,复写run方法 使用: MyThread target = new MyThread(); new Th ...
- Java学习笔记--线程day01
线程的概念:一个线程是进程的顺序执行流: 同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈.线程在切换时负荷小,因此,线程也被称为轻负荷进程.一个进程中可以有多个线程. ...
- Java学习笔记——线程
线程: 定义:线程是程序内的一个单一的顺序控制流程,也被称为“轻型进程(lightweight process)” 或“执行上下文(execution context )” 线程用于分隔任务 线程类似 ...
- Java学习笔记-多线程-创建线程的方式
创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
- java学习笔记14--多线程编程基础1
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...
随机推荐
- 精通libGDX-RPG开发实战
从今天开始,我会陆陆续续做一个五脏俱全的rpg小品游戏. 素材使用<圣剑英雄传II>的素材 游戏名称< Inspiration > 教程目录(暂定): Chapter 1: 开 ...
- 单元测试系列:Mock工具之Mockito实战
更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6780719.html 在实际项目中写单 ...
- MySQL修改密码的三种方法
MySQL修改密码的三种方法 1.方法1: 2.方法2: 3.方法3:
- 洛谷 [P1182] 数列分段
这是一道典型的二分答案问题(最大值最小,最小值最大)关键是对于细节的处理. 二分的框架: //l=max{num[i]},r=sum{num[i]} while(l<=r){ int m=(l+ ...
- bzoj 4546: codechef XRQRS [可持久化Trie]
4546: codechef XRQRS 可持久化Trie codechef上过了,bzoj上蜜汁re,看别人说要开5.2e5才行. #include <iostream> #includ ...
- POJ 3537 Crosses and Crosses [Multi-SG]
传送门 我也不知道为什么枚举vis必须加上一个边界才能A 以后还是都加上吧 #include <iostream> #include <cstdio> #include < ...
- 在ConcurrentModificationException异常上的联想
1.什么是ConcurrentModificationException? 大家都听说过快速报错fast-fail吧,fast-fail的发生就是说明发生了ConcurrentModification ...
- 阶段小项目1:循环间隔1秒lcd显示红绿蓝
#include<stdlib.h>#include<stdio.h>#include<string.h>#include<error.h>#inclu ...
- UITableView 之 点击cell 实现两个自定义cell之间的切换
- Linux下绝对经典的命令
1.使用远程终端时,可以使用如下命令: screen tmux 2.下载文件可以使用如下命令: curl wget 3.压缩解压缩可以使用: tar .zip.rar 4.使用抓包工具 tcpdump ...