1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1:实现Runnable接口创建多线程;正确的程序。
class A implements Runnable
{
public int tickets = 10000;
String str = new String("恒恒恒");

public void run()
{
while (true)
{
synchronized (str) // 同步代码块。
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_2
{
public static void main(String[] args)
{
A aa = new A();
Thread t1 = new Thread(aa);
t1.start();

Thread t2 = new Thread(aa);
t2.start();
}
}

理解:因为是实现了Runnable接口,所以不管是t1,t2都只是传递了类A造出来的一个对象aa,因此t1,t2所访问的tickets和str都是同一个类中的属性,并不用去设置为static。所以Tickets相同,同步代码块锁定的是同一个对象,因此程序正确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2:直接继承Thread类的方法;正确的程序。
class A extends Thread
{
public static int tickets = 1000; //static不能省
public static String str = new String("哈哈"); //static不能省

public void run() // 重写Thread类中的run方法,所以上两行的static不能省略。
{
while (true)
{
synchronized (str) // 同步代码块。
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_3
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.start();
// 堆中存在两个对象 ok!
A aa2 = new A();
aa2.start();
}
}

理解:继承Thread类的A类new出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的属性是static,所以aa1和aa2共同拥有tickets和str,所以同步代码块的对象相同,出售的tickets相同,因此程序正确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
3:直接继承Thread类的方法;正确的程序。
class A extends Thread
{
public static int tickets = 1000; //static不能省
public String str = new String("恒恒恒"); //static不能省

public void run() // 重写Thread类中的run方法,所以上两行的static不能省略。
{
String str = "恒子";
while (true)
{
synchronized (str) // 同步代码块。
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_4
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.start();
// 堆中存在两个对象
A aa2 = new A();
aa2.start();
}
}

理解:继承Thread类的A类new出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的tickets属性是static,所以aa1和aa2共拥tickets属性,但str是非static的,所以同步代码块中的对象并不是同一个对象,但是在执行run()方法时,又重新定义了在数据区的str,所以同步代码块绑定的还是同一个对象,因此该程序正确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
4:调用Runnable接口的方法;正确的程序。
class A implements Runnable
{
public static int tickets = 1000;
String str = new String("恒"); // new出来的str在堆区,两种调用的话会不是同一个str;

public void run()
{
String str = "恒子"; // 局部变量str, 在数据区,是同一个str;

while (true)
{
synchronized (str) // 需要synchronized(类对象名)
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_9
{
public static void main(String[] args)
{
A aa = new A();
A bb = new A();
Thread t1 = new Thread(aa);
t1.start();


Thread t2 = new Thread(bb);
t2.start();

}
}

理解:类A实现了Runnable接口,该程序与例1相似,但该程序类A new出了两个对象,因此开辟了两块空间,t1,t2的引用分别是aa和bb,并非同一个引用,但在类A中,Tickets是static的,所以t1,t2还是共同拥有同一个Tickets,但str对于t1,t2来说就不是同一个属性,可是,在执行run()方法的时候,里面定义了在数据区的局部变量str,这又重新使得同步代码块中t1,t2所绑定的对象是同一个对象。因此该程序也是正确的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
5:实现Runnable接口的方法;错误的程序。
class A implements Runnable
{
public static int tickets = 1000;
String str = new String("恒恒恒恒"); // new出来的str在堆区,两种调用的话会不是同一个str;

public void run()
{
while (true)
{
synchronized (str) // 需要synchronized(类对象名)
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_9
{
public static void main(String[] args)
{
A aa = new A();
A bb = new A();
Thread t1 = new Thread(aa);
t1.start();

Thread t2 = new Thread(bb);
t2.start();

}
}

理解:(对比例4),少了run()方法中的 String str = "恒子";所以同步代码块中的对象并未是统一对象,因此该程序错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
6:直接继承Thread类的方法;错误的程序。
class A extends Thread
{
public static int tickets = 1000; //static不能省
public String str = new String("恒恒恒"); //static不能省

public void run() // 重写Thread类中的run方法,所以上两行的static不能省略。
{
while (true)
{
synchronized (str) // 同步代码块。
{
if (tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
}
else
{
break;
}
}
}
}

}

public class TestTickets_4
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.start();
// 堆中存在两个对象
A aa2 = new A();
aa2.start();
}
}

理解:(对比例3)继承Thread类的A类new出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的tickets属性是static,所以aa1和aa2共拥tickets属性,但str是非static的,所以同步代码块中的对象并不是同一个对象,因此该程序错误。

Lock锁:

  • 从JDK5.0开始,java提供了更强大的线程同步机制—通过显示定义同步锁对象来实现同步。同步锁使用lock对象充当。
  • java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象。
  • ReentrantLock锁实现了lock,它拥有与synchronized相同的并发性和内存定义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Window implements Runnable{

private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while(true){
try{

//2.调用锁定方法lock()
lock.lock();

if(ticket > 0){

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}

}
}

}

public class LockTest {
public static void main(String[] args) {
Window w = new Window();

Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();
}

}

小Tips:

解决线程安全问题,有几种方式?

  • 3种,同步方法,同步代码块,lock锁。
  • 优先使用顺序:Lock 锁—-> 同步代码块(已经进入了方法体,分配了相应资源)—>同步方法(在方法体之外)

synchronized 与 Lock的异同?

  • 相同:二者都可以解决线程安全的问题。
  • 不同:
    • synchronized机制在执行完相应的同步代码以后,自动释放同步监视器。
    • Lock 需要手动的启动同步( lock() ),同时结束同步也需要手动的实现( unlock() )

synchronized与Lock的对比:

  • Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出现了作用域的自动释放。
  • Lock只有代码块锁,synchronized 有代码块锁和方法锁。
  • 使用lock 锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

创建线程的方式三:

在上面我们提到了继承Thread类和实现Runnable接口两种方法来创建线程,但是创建线程的方法不只是这两种呦,而且在开发中我们也不能每次用到线程都去造把,不要重复造轮子我们也都或多或少听别人谈起过,但是偶尔造造轮子也会让你对代码有更深刻的理解呦。

下面我们看一下创建线程的第三种方式:

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask

/**

* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增


* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的


*/

//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}


public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();

try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

}

创建线程的方式四:

这是我们开发中经常用的方式呦:

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**

* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止

*/

class NumberThread implements Runnable{

@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}

}

class NumberThread1 implements Runnable{

@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}

}

public class ThreadPool {

public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性

// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();


//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable

// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}

}