1. 线程安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。---百度词条
2. 不安全举例
当1000-200和1000+300同时进行时,会出现两种结果:800或1300,而不是正确的1100。
3. 实现线程安全
一个对象是否需要时线程安全的,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是synchronized。
3.1 线程互斥
当多个线程需要访问同一资源时,要求在一个时间段内只能允许一个线程来操作共享资源,操作完毕后别的线程才能读取该资源,这叫线程的互斥。我们需要使用synchronized来给共享区域加锁,确保共享资源安全。
如果一个线程调用了某个对象的synchronized方法,它在这个方法运行完之前不会被别的线程打断,这就是线程的同步机制。一般将共享资源放在这个同步方法内部,这样就保证在一个线程对这个资源操作完之后别的线程才可以访问。
将共享资源放在同步方法或者同步代码块中可以保证一个线程对共享资源操作时不会被打断,凡是被synchronized修饰的资源,在运行的时候系统都要给它们分配一个管理程序,这需要消耗一部分资源。4. 代码实现
4.1 不安全代码
public class ThreadSynchronized {
public static void main(String[] args) {
new ThreadSynchronized().init();
}
private void init() {
Outputer outputer = new Outputer();
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("zzzzzzzzzzzzzzzzzzzzzz");
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("!!!!!!!");
}
}
}.start();
}
class Outputer{
public void output(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));//一个字符一个字符的输出来的
}
System.out.println();
}
}
}
效果展示
显而易见,当一个线程还未执行完,另一个线程已经插入,这是线程不安全的。
4.2 安全代码:使用synchronized关键字
实现一:更改output方法中的内容,
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));//一个字符一个字符的输出来的
}
System.out.println();
}
实现二:在output方法前加synchronized关键字
public synchronized void output2(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
注:一般一个代码中只用一个synchronized。如果方法前使用synchronized,方法中还使用synchronized,有极大的可能出现死锁
5. 互斥展示
更改Output类代码如下:
static class Outputer{
public void output(String name){
int len = name.length();
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));//一个字符一个字符的输出来的
}
System.out.println();
}
}
public synchronized void output2(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
public static synchronized void output3(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
在ThreadSynchronized类中的两个线程调用不同的output方法:
测试一:output和output2,实现互斥效果。
测试二:output和output3,未实现互斥效果,效果如下:
测试三:将output方法中的synchronized (this)更改为synchronized (Outputer.class),再测试,实现互斥效果。
原因:Outputer.class,是字节码对象,不是实例对象,static静态类不能实例化,在程序执行时就启动了,所以用字节码对象可以实现互斥。