进程:在内存中正在执行的程序。一个应用程序如果想被执行,需要跑在内存中。
线程:是进程的一个执行单元,用来负责进程中程序(代码)的执行。在进程中可以有一条线程,也可以有多条线程。如果只有一条线程,称之为单线程程序。如果有多条线程,称之为多线程程序。一个进程至少需要一条线程。
java中线程使用抢占式调度:线程具有优先级,优先级高的抢占到线程CPU资源概率会更大。如果线程的优先级相同,那么会随机选择一个线程执行。优先级分为1~10,理论上数字越大,优先级越高,但实际上相邻的优先级之间差距非常不明显。可以使用**setPriority()**来设置优先级。
继承Thread类
定义一个类继承Thread。
重写run方法。
创建子类对象,就是创建线程对象。
调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法
定义类实现Runnable接口。
覆盖接口中的run方法。
创建Thread类的对象 将Runnable接口的子类。
对象作为参数传递给Thread类的构造函数。
调用Thread类的start方法开启线程。
既然提到线程,那肯定会提到线程安全问题,那什么是线程安全呢?如果不考虑线程安全,那会出现什么情况呢?我们用一个售票的例子来进行模拟:
// 模拟票数
public class Ticket implements Runnable{
int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true){
// 让当前线程休眠 单位是毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 说明票卖光了
if (ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
}
}
测试代码:
public static void main(String[] args) {
// 创建票对象
Ticket ticket = new Ticket();
// 通过Thread来模拟窗口
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
// 开启线程进行卖票
t1.start();
t2.start();
t3.start();
}
如果不考虑线程安全问题的话,那么会出现以下情况:
原因是当某个线程进入某句代码后,可能执行了一部分资源就被其他线程给抢了过去,从而导致该线程的数据进行了一般,可能刚输出完还没写入内存,这时被另一条线程抢走,此时数据还未进行修改,所以就出现了重复数据,而0,-1这种就是数量为1时刚通过上面的判断,但还未进行输出,这时资源被其他线程抢走,从而导致在数量为1时三条线程都通过了判断,再依次进行输出和自减操作。
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
而售票案例明显出了问题,就是存在线程安全隐患。
解决思路:保证核心代码同一时刻只能有一条线程在执行。
那么如和保证线程安全呢?一般我们使用这几种方法
- 同步代码块
格式:synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
方法区的内容是被线程共享的,所以"abc" Math.class 都可以当做同步代码块的锁资源 。
同步代码块会影响多线程的效率,所以只加核心可能出现隐患的代码。
- 同步方法
我们可以使用synchronized 修饰方法,可以保证方法中同一时刻只能有一条线程在执行。
同步方法的锁资源是this。
- 静态同步方法:
被synchronized和static修饰的方法,可以保证方法中同一时刻只能有一条线程在 执行。
静态同步方法的锁资源是 类名.class。
通过上面几种方法,我们可以修改一下我们的代码
// 模拟票数
public class Ticket implements Runnable{
int ticket = 100;
// 当做同步代码块的锁资源
Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while (true){
// 让当前线程休眠 单位是毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// // 同步代码块
synchronized (obj) {
// 说明票卖光了
if (ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
}
}
}
同步:只允许一个线程执行,此时线程是安全的;
异步:允许多线程同时执行,此时就有可能出现线程安全隐患。
线程安全隐患出现的条件:
解决线程安全的方法:
1.同步代码块;
锁资源是任何共享的对象。
2.同步方法;
锁资源是this。
3.静态同步方法;
锁资源是类名.class。
什么是原子操作呢”?
可以理解为类似于数据库的事务,毕竟事务也有原子性,这个(组)操作在执行时不会被其他原因打断,要么执行成功,要么全部失败,不存在一半成功一半失败的可能。
那么原子操作都有哪些呢?
(1)除long和double之外的基本类型的赋值操作
(2)所有引用reference的赋值操作
(3)java.concurrent.Atomic.* 包中所有类的一切操作
注:count++不是原子操作,因为它可以拆分为三个原子操作。
那为什么long和double的赋值不属于原子操作呢?
在网上看到某篇文章,大致意思如下:
1.对一个没有使用volatile修饰的long或double类型的赋值会被拆分成两次写,每次写该类型的32-bit数据,再使用volatile修饰后的long和double类型的读写操作是原子性的。
2.对其引用类型(Long/Double)的读写操作是属于原子操作,尽管他们的实现可能被分为两次32-bit或者一个64-bit。
死锁deadlock:
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
出现死锁的原因是有锁的嵌套,类似于一个线程A套着B,另线程一个B套着A,此时两个线程都在进行,A和B都被锁住,等着另一方释放资源,结果谁也不释放,造成死锁。
活锁
除死锁外还有一个更恐怖的bug——活锁。
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
工具:Eclipse,Oracle,smartupload.jar;语言:jsp,Java;数据存储:Oracle。...
项目中用到的一些特殊字符和图标 html代码 XML/HTML Code 复制内容到剪贴板 div ...
DELETEFROMTablesWHEREIDNOTIN(SELECTMin(ID)FROMTablesGROUPBYName) Min的话保...
正则忽略大小写 – RegexOptions.IgnoreCase 例如: 复制代码 代码如下: Str = R...
Elasticsearch 是通过 Lucene 的倒排索引技术实现比关系型数据库更快的过滤。特...
错误描述: 在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报...
4月11日20:30~22:00通过腾讯会议进行了第二次在线学习讨论我把学习笔记整理一下...
复制代码 代码如下: % URL="http://news.163.com/special/00011K6L/rss_newstop....
上篇文章给大家介绍了 Java正则表达式匹配,替换,查找,切割的方法 ,接下来,...
本文实例讲述了Laravel框架源码解析之反射的使用。分享给大家供大家参考,具体如...