java中所有的代码都是线程运行的.
main方法也不例外,当JVM启动后会自动创建一条线程并取名为"main",然后这条线程的run方法执行后会开始调用
我们写的main方法.
因此运行main方法的线程被我们称为主线程
ThreadLocal类,作用:在一个线程的执行流程中可以跨方法共享数据. 此时内部就使用了currentThread方法.获取执行某方法的线程
线程的优先级
线程有10个优先级,使用整数1-10表示
其中1表示最小优先级,10为最高优先级.5为默认值
当一个线程调用start方法后就纳入到了操作系统的线程调度器中被统一管理了.调度器会尽可能均匀分配时间片给 每个线程执行.线程不能主动索取时间片.
调整线程的优先级可以最大程度的干涉调度器分配时间片的概率.
优先级越高的线程获取时间片的次数越多
final static int MIN_PRIORITY = 1 表示线程可以允许的最小优先级
final static int NORM_PRIORITY = 5 表示线程创建时默认的优先级
final static int MAX_PRIORITY = 10 表示线程可以允许的最大优先级
hread提供了一个静态方法:
- static void sleep(long ms)
- 可以使执行这个方法的线程进入阻塞状态指定毫秒,超时后线程会自动回到RUNNABLE状态再次并发执行
sleep方法要求必须处理中断异常InterruptedException - 当一个线程调用sleep方法处于睡眠阻塞的过程中若该线程的interrupt方法被调用,那么此时会立即中断该线程的
- 睡眠阻塞,此时sleep方法会通过抛出异常告知这一现象.
**
守护线程
**
*
- java进程中至少要有一条用户线程活着,否则进程就会结束.如果进程在结束时发现还有守护线程活着会强行杀死这些线程.
- 守护线程是通过用户线程启动前调用方法setDaemon(true)设置而来的.
- 而用户线程就是我们默认创建的线程.
- 守护线程和用户线程是两类,不是说某个守护线程只守护某一个用户线程.
- 进程只要发现所有的用户线程都结束,就会结束自身,再次之前会杀死所有还在运行的守护线程.
- GC就是运行在守护线程上的
Thread:线程
多线程
- 线程:线性的执行流程,指的是程序中一个单一的顺序执行流程
- 多线程:多个顺序执行流程"同时"执行
- 第一种创建线程的方式:
- 1:定义一个类并继承Thread
- 2:重写run方法.
启动线程要调用线程的start方法,而不是直接调用run方法!当我们调用一个线程的start方法后,线程就启动了,随后run方法会很快被自动执行
这种创建方式的优点: - 结构简单,适合匿名内部类方式创建
- 缺点:
- 1:由于java是单继承的,这导致如果我们继承了Thread就无法再继承其它类去复用方法了.
- 2:重写run方法,将线程要执行的任务定义在线程中,这导致线程与任务是紧耦合的
线程的另一种创建方式:
- 实现Runnable接口单独定义线程任务
package thread;
/**
* 线程的另一种创建方式:
* 实现Runnable接口单独定义线程任务
*
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//单独实例化任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程并指派任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
/**
* 这里单独定义线程任务是实现Runnable接口(或者后期在线程池中实现Callable接口)
* 由于是实现接口,不再是直接继承Thread,因此没有继承冲突问题了.
* 线程与任务解耦,可以让线程重复使用(后期线程池中会看到这个情况)
*/
class MyRunnable1 implements Runnable{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("开门,查水表!");
}
}
}
使用匿名内部类来完成两种方式的线程创建
package thread;
/**
* 使用匿名内部类来完成两种方式的线程创建
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//第一种:继承Thread重写run方法
Thread t1 = new Thread(){
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
};
//第二种:单独定义线程任务
/* Runnable r2 = new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("我是查水表的");
}
}
};
Thread t2 = new Thread(r2);*/
//Runnable接口可以使用lambda表达式创建
/*Runnable r2 = ()->{
for (int i = 0; i < 1000; i++) {
System.out.println("我是查水表的");
}
};
Thread t2 = new Thread(r2);*/
Thread t2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
System.out.println("我是查水表的");
}
});
t1.start();
t2.start();
}
}
- 获取一个线程相关信息的方法
package thread;
/**
* 获取一个线程相关信息的方法
*/
public class ThreadInfoDemo {
public static void main(String[] args) {
// Thread t = new Thread("nice"){//Thread实例化时可以自行指定名字
Thread t = new Thread(){//若不指定名字则使用系统分配的名字
public void run(){
/*
String getName()
获取线程的名字
我们定义的线程若没有显示的指定名字是,系统会分配一个名字.格式:Thread-X
X是一个数字
*/
String name = this.getName();
System.out.println(name);
/*
可以作为ID只用的值必须同时具备两个要求:
1:非空
2:唯一
通常id都是由系统分配,而不是人为干预.
*/
long id = this.getId();//id:唯一标识
System.out.println(id);
/*
int getPriority()
获取线程的优先级
*/
int priority = this.getPriority();
System.out.println(priority);
//线程是否被中断
boolean isInterrupted = this.isInterrupted();
//线程是否为守护线程
boolean isDaemon = this.isDaemon();
//线程是否还活着
boolean isAlive = this.isAlive();
System.out.println("是否被中断:"+isInterrupted);
System.out.println("是否是守护线程:"+isDaemon);
System.out.println("是否还活着:"+isAlive);
}
};
t.start();
}
}
多线程并发安全问题
- 多线程并发安全问题
- 当多个线程并操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序混乱,结果产生了不良影响.
- 临界资源:操作该资源的完整过程同一时刻只能被单个线程进行.
* 当一个方法被synchronized关键字修饰后,此方法称为"同步方法".这种方法的特点是: * 同一时刻只能有一个线程在方法内部执行. * 将多个线程抢着执行该方法变为排队执行该方法就可以有效的解决并发安全问题了.
package thread;
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread("甲"){
public void run(){//若一个异常抛出到run方法之外,意味着这个线程也会结束
while(true){
int bean = table.getBean();//20
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread("乙"){
public void run(){//若一个异常抛出到run方法之外,意味着这个线程也会结束
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
public synchronized int getBean(){//从桌子上拿一个豆子
if(beans==0){
throw new RuntimeException("没有豆子了");
}
//使用主动放弃时间片来模拟线程执行到此时没有时间了
Thread.yield();//让运行该方法的线程主动放弃本次剩余时间片
return beans--;
}
}
* 同步块
-
语法:
-
synchronized(同步监视器对象){
-
需要多个线程同步执行的代码片段(多线程会产生并发安全问题的代码)
-
}
-
有效的缩小同步范围是可以在保证并发安全的前提下尽可能提高并发效率
同步块可以更精准的控制需要同步执行的代码片段
但是使用同步块时要指定同步监视器对象,同步监视器对象要同时具备以下条件:
1:必须是引用类型实例
2:多个需要同步执行该代码片段的线程看到的必须是同一个对象合适的锁对象,应当是在多个线程出现"抢"的时候发挥左右,否则不发发挥作用 例如:两个线程分别执行两个Shop实例的buy方法(相当于两个人去不同的商店)时,那么这两个 线程就不发生"抢"的现象,此时下面的同步块就不应当要求线程排队执行代码. 如果同步监视器对象选取了字符串字面量(字符串字面量在任何情况下都是同一个对象),那么在 这样的情况下也要求多个线程排队执行显然是不合适的.
synchronized不是公平锁,当一个线程进入后,如果后续有5个线程陆续执行到这里开始排队时
当进入的线程出了同步块后,并不是后五个中先排队的线程先进入,而是后五个线程谁先拿到时间片
谁先进入执行.
如果想实现公平锁,可以使用JUC(java.util.concurrent包),java并发包
这个包中包含了很多和并发相关的API.
package thread;
public class SyncDemo2 {
public static void main(String[] args) {
// Shop shop = new Shop();
Shop shop1 = new Shop();
Shop shop2 = new Shop();
Thread t1 = new Thread("王克晶"){
public void run(){
// shop.buy();
shop1.buy();
}
};
Thread t2 = new Thread("范传奇"){
public void run(){
// shop.buy();
shop2.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
/*
在方法上使用synchronized时,同步监视器对象不可选,只能是this.
*/
// public synchronized void buy(){
public void buy(){
try {
Thread t = Thread.currentThread();//获取运行buy方法的线程
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
synchronized (this) {
// synchronized (new Object()) {//无效的锁对象,因为多个线程看到的不是同一个对象
// synchronized ("hello"){//有效但不合适
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
静态方法锁
-
在一个静态方法上如果使用synchronized后,同样的锁对象不可选,但不是this,而是当前类的类对象(Class的
-
一个实例)
-
注:类对象会在后去反射课程中详细介绍
-
静态方法上如果使用synchronized,那么该方法一定是同步的.
静态方法中如果使用同步块时,指定的同步监视器对象应当也是类对象.
指定时的语法:类名.class注: 类对象指的是Class的一个实例.当我们使用到一个类时,JVM就会去加载这个类的.class文件,并且 随之生成一个Class的实例来保存加载的这个类的信息,在JVM内部每个被加载的类都有且只有一个Class 的实例与之对应.
package thread;
public class SyncDemo3 {
public static void main(String[] args) {
// Boo b1 = new Boo();
// Boo b2 = new Boo();
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
// b1.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
// b2.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
// public synchronized static void dosome(){
public static void dosome(){
synchronized (Boo.class) {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕");
} catch (InterruptedException e) {
}
}
}
}
-
互斥性
-
当使用多个synchronized锁定多个代码片段,并且指定的同步监视器对象相同时,这些代码片段就是互斥的.
-
多个线程不能同时执行这些代码片段.
package thread;
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void methodA(){
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}