JUC並發編程
1、什么是JUC
JUC即java.util.concurrent
**涉及三個包: **
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
2、線程和進程
2.1 什么是線程和進程
**進程: **一個程序。如QQ、wechat等
**線程: **程序中的 某些操作。如打開了Typora,輸入、保存等就是線程。
一個進程至少包含一個線程!
java中默認有兩個線程,一個是main線程,另外一個是GC線程。
**java能自己開啟線程嗎? **
答:不能,java是通過調用本地c++方法開啟的線程。如下圖所示:
2.2 並發與並行的區別
**並發: **單核CPU模擬出多條線程,不斷交替,為快不破。
**並行: **多核CPU,多線程同時執行。
如何查看CPU核數?
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());
}
2.3 線程的六種狀態
Thread中有個內部枚舉類Thread.State其中定義了線程的六種狀態,源碼如下:
/** 新生
* Thread state for a thread which has not yet started.
*/
NEW,
/** 運行
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/** 阻塞
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/** 一直等待,等到地老天荒
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/** 有時間的等待
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/** 線程終止
* Thread state for a terminated thread.
* The thread has completed execution.
*/
2.4 wait與sleep的區別
-
來着不同的類
wait =>Object,sleep=>Thread
-
關於鎖得釋放
wait =>釋放鎖,sleep=>占用鎖,抱着鎖睡
-
使用范圍
wait =>同步代碼塊中使用,sleep=>想在哪睡在哪睡,任何地方使用
3、Lock鎖
3.1 什么是Lock鎖
如下圖:
3.2 如何使用
Lock lock = new ReentrantLock();
//加鎖
lock.lock();
try{
//業務代碼
}finally {
//解鎖
lock.unlock();
}
3.3 Lock與Synchronize的區別
-
來源不同
synchronize => java的內置關鍵字,在jvm層;Lock =>java的一個接口
-
獲取鎖得方式不同
synchronize => 自動獲取鎖,不能判斷鎖得狀態;Lock => 手動獲取鎖,可判斷是否獲取到鎖
-
線程阻塞方面
synchronize => 線程1阻塞會導致線程2永遠等待;Lock=>不一定會等下去
-
鎖得類型不同
synchronize=>可重入鎖、不可中斷、非公平;Lock=>可重入鎖、可判斷鎖、非公平(可設置成公平)
-
使用范圍不同
synchronize=>適用於少量代碼塊同步;Lock=>適合鎖大量的同步代碼塊
3.4 synchronize與lock模擬多線程搶票
synchronize
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"A").start();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"B").start();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"C").start();
}
}
class Ticket{
private static int ticketAmount = 100;
public synchronized void saleTicket(){
if(ticketAmount>0){
System.out.println(Thread.currentThread().getName()+"購買了第"+ticketAmount--+"張票");
}
}
}
Lock
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"A").start();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"B").start();
new Thread(() ->{
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
},"C").start();
}
}
class Ticket{
private static int ticketAmount = 100;
public void saleTicket(){
//創建lock鎖
Lock lock = new ReentrantLock();
//加鎖
lock.lock();
try {//業務代碼
if(ticketAmount>0){
System.out.println(Thread.currentThread().getName()+"購買了第"+ticketAmount--+"張票");
}
}finally {
//解鎖
lock.unlock();
}
}
}
4、生產者和消費者模式
線程之間的通信問題就是生產者和消費者問題,即線程的交替執行。比如兩個線程同時操作一個變量,就涉及到通知、等待和喚醒。其實線程間的通信主要就是通知、業務執行和喚醒。
4.1 synchronize版的生產者和消費者模式
public class Test {
public static void main(String[] args) {
PC pc = new PC();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"B").start();
}
}
class PC{
private static int amount=0;
public synchronized void add(){
if (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount++;
System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
//喚醒其他線程
notifyAll();
}
public synchronized void decrease(){
if(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount--;
System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
//喚醒其他線程
notifyAll();
}
}
執行結果如下圖:
4.2 synchronize模擬生產者和消費者模式產生的問題和解決方法
同樣是4.1中的代碼如果同時開啟4個(超過2個線程)代碼如下
public class Test {
public static void main(String[] args) {
PC pc = new PC();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"D").start();
}
}
class PC{
private static int amount=0;
public synchronized void add(){
if (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount++;
System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
//喚醒其他線程
notifyAll();
}
public synchronized void decrease(){
if(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount--;
System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
//喚醒其他線程
notifyAll();
}
}
執行結果如下圖:
上圖中的執行出問題了,得到的結果並不是0,1了。出現了意料之外的結果。為什么會出現這樣的結果?
答:從執行結果中我們發現感覺是線程B等待失效。然后我們去官方文檔中找到Object類中的wait方法,我們看到這樣一段話,如下圖:
按照jdk幫助文檔的提示,造成錯誤的原因是wait是不占用鎖的,在多個線程同時被喚醒的時候有一個隨機搶鎖的過程,導致了不該喚醒的線程搶到了鎖被喚醒了,即虛假喚醒。解決方法也很簡單,就是將判斷方法中的if改成while即可。while與if的不同在於,當等待的線程被喚醒后,if判斷的線程會直接往下走,而while判斷的線程需要重新判斷才能往下走。所以jdk官方文檔推薦使用while代替if從而解決虛假喚醒的現象。
4.3 Lock鎖模擬生產者和消費者模式
使用Condition工具類去作為Object監視器,用signal()和await()方法代替synchronized中的notify()和wait()
Condition的優勢在於能精准的喚醒線程
public class Test02 {
public static void main(String[] args) {
PC2 pc = new PC2();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"D").start();
}
}
class PC2{
private static int amount=0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void add(){
lock.lock();
try{
while(amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
try {
//等待
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount++;
System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
//喚醒其他線程
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decrease(){
lock.lock();
try{
while(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
try {
//等待
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount--;
System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
//喚醒其他線程
condition.signalAll();
}finally {
lock.unlock();
}
}
}
執行結果如下圖:
4.4 Lock鎖實現精確喚醒
實現目標:A、B、C、D四個線程順序執行。
public class Test02 {
public static void main(String[] args) {
PC2 pc = new PC2();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.add();
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
pc.decrease();
}
},"D").start();
}
}
class PC2{
private static int amount=0;
Lock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
Condition conditionD = lock.newCondition();
public void add(){
lock.lock();
try{
while (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
try {
//等待
if("A".equals(Thread.currentThread().getName())){
conditionA.await();
}else if("C".equals(Thread.currentThread().getName())){
conditionC.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount++;
System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
//喚醒其他線程
if("A".equals(Thread.currentThread().getName())){
conditionB.signal();
}else if("C".equals(Thread.currentThread().getName())){
conditionD.signal();
}
}finally {
lock.unlock();
}
}
public void decrease(){
lock.lock();
try{
while(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
try {
if("B".equals(Thread.currentThread().getName())){
conditionB.await();
}else if("D".equals(Thread.currentThread().getName())){
conditionD.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
amount--;
System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
//喚醒其他線程
if("B".equals(Thread.currentThread().getName())){
conditionC.signal();
}else if("D".equals(Thread.currentThread().getName())){
conditionA.signal();
}
}finally {
lock.unlock();
}
}
}
執行結果:
5、八個關於鎖的問題
5.1 先發短信還是先打電話
public class One {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread( () -> {
phone.call();
},"A").start();
new Thread( () -> {
phone.sendMsg();
},"B").start();
}
}
class Phone{
public synchronized void call(){
System.out.println("打電話");
}
public synchronized void sendMsg(){
System.out.println("發短信");
}
}
先打電話,再發短信
5.2 延遲后是先發短信還是先打電話
public class Two {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread( () -> {
phone.call();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread( () -> {
phone.sendMsg();
},"B").start();
}
}
class Phone2{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public synchronized void sendMsg(){
System.out.println("發短信");
}
}
還是先打電話再發短信
5.3 兩個對象調用不同的方法,執行的先后順序
public class Three {
public static void main(String[] args) {
Phone3 phoneA = new Phone3();
Phone3 phoneB = new Phone3();
new Thread( () -> {
phoneA.call();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread( () -> {
phoneB.sendMsg();
},"B").start();
}
}
class Phone3{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public synchronized void sendMsg(){
System.out.println("發短信");
}
}
先發短信后打電話
5.4 一個同步方法一個普通方法,誰先執行
public class Four {
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread( () -> {
phone.call();
},"A").start();
new Thread( () -> {
phone.sendMsg();
},"B").start();
}
}
class Phone4{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public void sendMsg(){
System.out.println("發短信");
}
}
先執行普通方法發短信,再執行打電話
5.5 同步方法都是靜態方法,同一個對象,先執行哪個
public class Five {
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread( () -> {
phone.call();
},"A").start();
new Thread( () -> {
phone.sendMsg();
},"B").start();
}
}
class Phone5{
public static synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public static synchronized void sendMsg(){
System.out.println("發短信");
}
}
先執行打電話,后執行發短信。
5.6 兩個對象調用不同的靜態同步方法,誰先執行
public class Six {
public static void main(String[] args) {
Phone6 phoneA = new Phone6();
Phone6 phoneB = new Phone6();
new Thread( () -> {
phoneA.call();
},"A").start();
new Thread( () -> {
phoneB.sendMsg();
},"B").start();
}
}
class Phone6{
public static synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public static synchronized void sendMsg(){
System.out.println("發短信");
}
}
先執行打電話,然后執行發短信
5.7 一個靜態同步方法,一個普通同步方法,只有一個對象,先執行哪個
public class Seven {
public static void main(String[] args) {
Phone7 phone = new Phone7();
new Thread( () -> {
phone.call();
},"A").start();
new Thread( () -> {
phone.sendMsg();
},"B").start();
}
}
class Phone7{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public static synchronized void sendMsg(){
System.out.println("發短信");
}
}
先執行發短信,再執行打電話
5.8 一個靜態同步方法,一個普通同步方法,兩個對象,先執行哪個方法
public class Eight {
public static void main(String[] args) {
Phone8 phoneA = new Phone8();
Phone8 phoneB = new Phone8();
new Thread( () -> {
phoneA.call();
},"A").start();
new Thread( () -> {
phoneB.sendMsg();
},"B").start();
}
}
class Phone8{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打電話");
}
public synchronized void sendMsg(){
System.out.println("發短信");
}
}
先執行發短信,后執行打電話
5.9 總結
鎖只會鎖兩個東西,一個是new 出來的對象,另外一個是唯一的class模板。普通的synchronize修飾的同步方法或同步代碼塊,鎖得是調用該方法的對象。static修飾的同步方法或同步代碼塊,鎖的是class唯一的模板,因此調用該同步方法的線程其對象是否一樣,鎖都是同一個。
6、集合不安全
6.1 ArrayList線程不安全
-
- 不安全的ArrayList
我們開啟多個線程向同一個list中添加,結果如下圖所示:
- 2.ArrayList線程不安全的原因
我們進入ArrayList的源碼可以看到以下代碼:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
我們從源碼中可以發現,ArrayList的底層是個Object數組。在add方法中,先是多數組長度加1,然后將需要添加的對象加到數組的最后。這樣就會存在一個問題,就是多個線程同時對數組長度進行了加1,后面線程的值將覆蓋前面線程的值。
- 3.如何解決ArraList線程不安全問題
public class UnSafeArrayList {
public static void main(String[] args) {
//1.將ArrayList 改為 Vector
//List<String> list = new Vector<>();
//2.用Collections工具類,將ArrayList轉為線程安全的集合類
//List<String> list = Collections.synchronizedList(new ArrayList<>());
//3.用CopyOnWriteArrayList 代替ArrayList
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread( () ->{
list.add(Thread.currentThread().getName());
},""+i).start();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
總結:Vector之所以是線程安全的,是因為Vector中的add方法是用synchronize修飾的;Collections.synchronizedList()之所以線程安全,是因為Collections.synchronizedList()得到了一個SynchronizedList對象,SynchronizedList中的add、set、get等方法中都使用了synchronized代碼塊;CopyOnWriteArrayList 之所以是線程安全的是因為CopyOnWriteArrayList 的原理是寫入時復制,即在在執行add方法時,將原數組復制一份(長度+1),然后將需要添加的對象加到復制數組的末尾,最后用復制的數組替代原數組,在add方法中使用的是Lock鎖,從而保證線程安全,源碼如下:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
綜上所述推薦使用CopyOnWriterArrayList代替ArrayList解決ArrayList的線程不安全問題,理由是synchronize等於低效率。
6.2 HashSet線程不安全
- 1.不安全的HashSet
我們開啟多個線程向同一個set中添加,結果如下圖所示:
- 2.Set線程不安全的原因
HashSet的底層就是HashMap
public HashSet() {
map = new HashMap<>();
}
//hashSet的add方法 本質是hashMap的put,set的值就是map中的key,PRESENT是一個常量,所有set是不可重復的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- 3.如何解決HashSet的線程不安全問題
public class UnSafeHashSet {
public static void main(String[] args) {
//Set<String> list = new HashSet();
//1.用Collections工具類將HashSet轉為線程安全的集合類SynchronizedSet
//Set<String> list = Collections.synchronizedSet(new HashSet<>());
//2.用CopyOnWriteArraySet代替HashSet
Set<String> list = new CopyOnWriteArraySet();
for (int i = 0; i < 30; i++) {
new Thread( () -> {
list.add(Thread.currentThread().getName());
System.out.println(list);
},""+i).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
6.3 HashMap線程不安全
- 1.不安全的HashMap
- 2.如何解決HashMap的線程不安全問題
public class UnSafeHashMap {
public static void main(String[] args) {
//1. map 是這么用的嗎? 不是,工作中不用HashMap
//2. 默認等價於什么? new HashMap<>(16,0.75)
//Map<String,String> map = new HashMap<>();
//1.用Collections工具類將HashMap轉變成線程安全的SynchronizedMap
//Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
//2.用ConcurrentHashMap 代替HashMap
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread( () -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7、Callable
- 1.可以有返回值
- 2.可以拋出異常
- 3.方法不同,run()/call()
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
//Thread中只能傳Runnable對象,Callable對象無法傳入,怎么解決?
//我們在官方文檔中找到Runnable接口的一個實現類——FutureTask,其中有個構造方法可傳入Callable對象
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
//多個線程調用只會打印一次call(),結果有緩存
new Thread(futureTask,"B").start();
//獲取返回值
//可能會阻塞,因為如果call方法中是一個耗時的業務將導致阻塞
//解決阻塞的辦法:1.放到最后,2.異步通信
Integer o = (Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}