1.多线程的创建
多线程创建的方式有两种:
1.通过继承Thread类来实现多线程
2.通过实现Runnable接口来实现多线程
下面我们分别介绍这两种多线程的创建方式
下面我们来看这两种实现方式:
package gt;
/**
* Created by Cronous on 2017/10/30.
* 创建线程的第一种方式继承Thread类
*/
public class day02 {
/*
Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务的描述
这个任务就是Thread类中的run方法,也就是说,run方法就是封装自定义线程
运行任务的函数
run方法中定义的就是线程要运行的任务代码
*/
public static void main(String[] args){
Demo2 d1 = new Demo2("旺财");
Demo2 d2 = new Demo2("小强");
d1.start();
///d2.start();
Demo1 demo1 = new Demo1("tom");
Demo1 demo2 = new Demo1("jerry");
Thread t1 = new Thread(demo1);
Thread t2 = new Thread(demo2);
t1.start();
t2.start();
}
}
/*
第一种方式
1.定义一个类继承Thread类
2.覆盖Thread类中的run方法
3.直接创建Thread类的子类对象
4.调用 start()方法并调用线程的任务
第一种方式有个弊端,如果这个类已经有父类了,就存在这个这个问题了
可以通过 Thread.getName()获取线程名称
我们来看第二种方式,实现 Runnable接口
1.实现接口
2.创建Thread对象,然后将new的对象放入 Thread中
让线程对象调用
*/
class Demo2 extends Thread{
private String name;
Demo2(String name){
super(name);
}
@Override
public void run() {
show();
}
public void show(){
for(int i = 0;i < 10;i++){
System.out.println(name + "...i=" + i + " "+ Thread.currentThread());
}
}
}
//第二种方式 实现 Runnable接口
class Demo1 implements Runnable{
private String name;
Demo1(String name){
this.name = name;
}
@Override
public void run() {
show();
}
public void show(){
for(int i = 0;i < 10;i++){
System.out.println(name + "...i=" + i + " "+ Thread.currentThread());
}
}
}
这里我们可以发现:如果某个类已经有父类了,我们就无法继承Thread类,
这时就存在这个问题了,我们需要使用实现Runnable接口
1.我们需要把覆盖run方法
2.将需要多线程执行的代码放入run函数中
3.如果是继承Thread类直接可以new线程类,然后调用start()方法
4.如果是实现Runnable接口,则new Runnable对象,使用Thread构造方法
Thread(Runnable r)
进行构造Thread线程对象,之后调用start()
方法
控制台结果:
tom...i=0 Thread[Thread-0,5,main]
tom...i=1 Thread[Thread-0,5,main]
tom...i=2 Thread[Thread-0,5,main]
tom...i=3 Thread[Thread-0,5,main]
tom...i=4 Thread[Thread-0,5,main]
tom...i=5 Thread[Thread-0,5,main]
tom...i=6 Thread[Thread-0,5,main]
tom...i=7 Thread[Thread-0,5,main]
tom...i=8 Thread[Thread-0,5,main]
tom...i=9 Thread[Thread-0,5,main]
jerry...i=0 Thread[Thread-1,5,main]
jerry...i=1 Thread[Thread-1,5,main]
jerry...i=2 Thread[Thread-1,5,main]
jerry...i=3 Thread[Thread-1,5,main]
jerry...i=4 Thread[Thread-1,5,main]
jerry...i=5 Thread[Thread-1,5,main]
jerry...i=6 Thread[Thread-1,5,main]
jerry...i=7 Thread[Thread-1,5,main]
jerry...i=8 Thread[Thread-1,5,main]
jerry...i=9 Thread[Thread-1,5,main]
2.多线程的安全问题
我们看下面的一个卖票案例,有一百张票,多个线程同时进行售票
class day02{
public static void main(String[] args){
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread{
private int num = 100;
public void run(){
while(true){
if(num > 0){
try{
}catch(InterruptedException e)
{}
System.out.println(Thread.currentThread().getName()+
"....sale..."+ num--);
}
}
}
}
控制台会有这样的结果:
Thread-2....sale... 1
Thread-1....sale... 1
我们发现两个线程居然重复进行了1号票的出售,这种结果是有问题的
线程产生问题的原因:
1.多个线程在操作共享数据
2.操作共享数据的线程代码有多条,如上面的代码 if(num > 0)是一条,
System.ou.println()输出又是一条,线程1可能执行到第一条语句就停在那儿了,并没有输出num,但是第二个线程可能就会执行到输出,所以导致原先应该是线程1输出的num被线程2输出,并且因为线程1保留着状态,所以等他得到执行权还是会输出原先的num,这样就导致了重复num的出现,从而导致安全问题
3.安全问题的解决
3.1 同步代码块
解决思路:
将多条操作共享的线程代码封装起来,当有线程在执行这些代码的时候,
其他的线程是不可以参与运算的,必须等待当前线程执行完毕后,其它线程才可以执行
同步代码块可以解决这个问题:
格式:synchronized(对象)
{
//需要被同步的代码块
}
class day02{
public static void main(String[] args){
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread{
private int num = 100;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if(num > 0){
try{
}catch(InterruptedException e)
{}
System.out.println(Thread.currentThread().getName()+
"....sale..."+ num--);
}
}
}
}
}
这样问题即可解决
同步代码块的好处与弊端:
好处:解决了线程安全问题
坏处:一定程度上影响了执行效率,因为同步外的线程都会判断同步锁,这里的锁即同步中的对象
同步的前提:必须有多个线程并使用同一个锁
我们看一个不是使用一个锁的情况,很简单,只需要将Object obj = new Object()放在执行函数run里面即可,这样每次new线程对象的时候obj就不是同一个对象,这就是不同的锁,修改代码如下:
class day02{
public static void main(String[] args){
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread{
private int num = 100;
//Object obj = new Object();
public void run(){
Object obj = new Object();
while(true){
synchronized(obj){
if(num > 0){
try{
}catch(InterruptedException e)
{}
System.out.println(Thread.currentThread().getName()+
"....sale..."+ num--);
}
}
}
}
}
控制台打印:
Thread-2....sale... 1
Thread-1....sale... 0
Thread-0....sale... -2
出现了负数票,不合常理,出现错误,原因:用的不是同一个锁
3.2 同步函数
我们看一个银行的例子:储户向银行存钱行为
package gt;
/**
* Created by Cronous on 2017/10/31.
*
* 这里我们可以使用同步函数 同步代码块在函数内,所以可以将函数进行同步
*/
public class BankDemo {
public static void main(String[] args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Bank{
private int sum;
//private Object obj = new Object();
public void add(int num){
sum = sum + num;//这里代码会出并发问题
System.out.println("sum=" + sum);
}
}
class Cus implements Runnable{
Bank b = new Bank();
@Override
public void run() {
//Bank b = new Bank();这样的意思是两储户是去两银行存钱
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int x = 0;x < 10;x++){
b.add(100);
}
}
}
控制台打印:
...
...
sum=1700
sum=1400
sum=1800
sum=1900
居然显示了1900,正确应该是2000
我们会发现出问题的代码在函数内部,函数本身也是一种封装,所以我们可以让函数具备同步性
这里我们可以使用另一种方式:同步函数的方式来解决
class Bank{
private int sum;
//private Object obj = new Object();
public synchronized void add(int num){
sum = sum + num;//这里代码会出并发问题
System.out.println("sum=" + sum);
}
}
只需要在函数前加上修饰符 synchronized
这里我们就有个问题,同步代码块的锁是对象,那么同步函数的锁是谁?
我们继续卖票例子:
public class tikets implements Runnable{
private static int num = 100;
Object obj = new Object();
boolean flag = true;
@Override
public void run(){
System.out.println("this" + this);
if(flag){
while(true){
synchronized(obj){
if(num > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num-- + "....." + Thread.currentThread());
}
}
}
}else{
while(true){
show();
}
}
}
public synchronized void show(){
if(num > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num-- + ".....function" + Thread.currentThread());
}
}
}
class test{
public static void main(String[] args){
//System.out.println("Hello,world!");
tikets t1 = new tikets();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
thread1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.flag = false;
thread2.start();
}
}
设置一个flag,为true执行同步代码块,false执行同步函数
控制台打印结果如下:
...
...
5.....Thread[Thread-0,5,main]
4.....functionThread[Thread-1,5,main]
2.....functionThread[Thread-1,5,main]
3.....Thread[Thread-0,5,main]
1.....functionThread[Thread-1,5,main]
1.....Thread[Thread-0,5,main]
我们会发现Thread-1
与Thread-2
都卖了1号票,说明同步代码块和同步函数所使用的锁是不一样的
函数是被对象调用的,所以函数持有对象this,所以同步函数的锁即调用它的对象
1.同步函数的锁是固定的this
2.同步代码块锁是任意的对象
建议使用同步代码块
那么静态同步函数的锁是谁?—静态函数没有this
分析:静态函数是随着类的加载而加载,java对象建立都有自己的字节码文件对象,所以静态函数的锁即是它的类对象 synchronized(this.getClass()),如果在静态方法中则为synchronized(Xxxx.class)
下面的show()方法静态修饰了,我们看同步代码块如何修改的
public class tikets implements Runnable{
private static int num = 100;
Object obj = new Object();
boolean flag = true;
@Override
public void run(){
System.out.println("this" + this);
if(flag){
while(true){
//这里做修改this.getClass
synchronized(this.getClass()){
if(num > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num-- + "....." + Thread.currentThread());
}
}
}
}else{
while(true){
show();
}
}
}
public static synchronized void show(){
if(num > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num-- + ".....function" + Thread.currentThread());
}
}
}
class test{
public static void main(String[] args){
//System.out.println("Hello,world!");
tikets t1 = new tikets();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
thread1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.flag = false;
thread2.start();
}
}